#! /bin/sh
#!perl -w # --*- Perl -*--
eval 'exec perl -x $0 ${1+"$@"}'
    if 0;
#------------------------------------------------------------------------------
#$Author: andrius $
#$Date: 2017-05-30 14:51:03 +0300 (Tue, 30 May 2017) $ 
#$Revision: 5376 $
#$URL: svn://www.crystallography.net/cod-tools/tags/v2.1/scripts/cif_Fcalc $
#------------------------------------------------------------------------------
#*
#* Compute the structure factors from CIF files.
#*
#* USAGE:
#*    $0 --options input1.cif input*.cif
#**

# The implementation in this programm follows the principles published
# in:

# (Yvon1977) Yvon, K.; Jeitschko, W. & Parthé, E. it LAZY PULVERIX, a
# computer program, for calculating X-ray and neutron diffraction
# powder patterns Journal of Applied Crystallography, 1977, 10, 73-74
# http://dx.doi.org/10.1107/S0021889877012898

# Exponential form of the structure factor was expanded how in 
# (Wallwork, S. C., Introduction to the calculation of structure factors, 
# published Cardiff [Wales] : Published for the International Union of 
# Crystallography by University College Cardiff Press, 1980.)
# http://www.iucr.org/__data/assets/pdf_file/0015/13083/3.pdf

use strict;
use warnings;
use COD::CIF::Parser qw( parse_cif );
use COD::CIF::Data::AtomList qw( atom_array_from_cif );
use COD::CIF::Data qw( get_cell );
use COD::Algebra::Vector qw( distance );
use COD::Spacegroups::Symop::Parse qw( symop_from_string modulo_1 );
use COD::Spacegroups::Symop::Algebra qw( symop_vector_mul );
use COD::CromerMann;
use COD::CIF::Data qw( get_symmetry_operators );
use COD::Fractional qw( symop_ortho_from_fract );
use COD::SOptions qw( getOptions );
use COD::SUsage qw( usage options );
use COD::ErrorHandler qw( process_warnings
                          process_errors
                          process_parser_messages
                          report_message );
use COD::ToolsVersion;

my $use_parser = 'c';

my $die_on_error_level = {
    'ERROR'   => 1,
    'WARNING' => 0,
    'NOTE'    => 0
};

my $dump_xyz_coordinates = 0;
my $dump_Cromer_Mann_parameters = 0;
my $dump_cell_parameters = 0;
my $dump_cell_xyz_coordinates = 0;
my $iso_temperature_factor = 0;
my $max_resolution = 1.5; # angstrom
my $dump_atoms_and_neighbors = 0;
my @hkl_default = (10, -10, 10, -10, 10, -10); # hmax, hmin, k..., l...
# constants;
my $dump_descending_sort_Fhkl = 0;
my $dump_test_Fhkl = 0; # 
my $max_number_print_of_F;
my $Miller_indexes;
my $external_CR_table = 0;
my $hkl_file;
my $PI = 4*atan2( 1, 1 );

#* OPTIONS:
#*   --max-Fhkl-number
#*                     Selection of sorted (numerically and Friedel's low)
#*                     Fhkl number.
#*   --external_CR_table
#*                     Use Cromer Mann coefficients from external
#*                     source: table 6.1.1.4.
#*   --max-resolution
#*                     Selection of resolution.
#*   --isotropic-Tf
#*                     Use isotropic temperature factor for structure
#*                     factor calculations.
#*   --Miller-indexes "5 6 7 -1 2 -6"
#*                     Provide limits on the Miller indices
#*                     (hmax, hmin, kmax, kmin, lmax, lmin).
#*                     Default values are (10, -10, 10, -10, 10, -10).
#*   --get-hkl-data
#*                     Providing of a path to COD file which contains
#*                     hkl data.
#*   --dump-xyz-coordinates
#*                     Print an XYZ file 
#*   --dump-Cromer-Mann
#*                     Print a Cromer-Mann coefficients from CIF file.
#*   --dump-cell-parameters
#*                     Print a short usage message with cell parameters.
#*   --dump-sorted-F
#*                     Sort Fhkl numerically descending.
#*   --dump-atoms-and-neighbors
#*                     Print a labels of atoms and their neighbors from
#*                     a CIF file.
#*   --dump-test-Fhkl
#*                     Print a following list:
#*                            h k l
#*                            F(phase)^2
#*                            F(phase, T.factor)^2
#*                            F(phase, T.factor,anomalous-dispersion)^2
#*   --dump-cell-xyz-coordinates
#*                     Print a XYZ file which contains all atoms of cell.
#*
#*   --use-perl-parser
#*                     Use development CIF parser written in Perl.
#*   --use-c-parser
#*                     Use faster C/Yacc CIF parser (default).
#*
#*   --help, --usage
#*                     Output a short usage message (this message) and exit.
#*   --version
#*                     Output version information and exit.
#**
@ARGV = getOptions(
    '--dump-xyz-coordinates'       => sub { $dump_xyz_coordinates = 1 },
    '--dump-Cromer-Mann'           => sub { $dump_Cromer_Mann_parameters = 1 },
    '--max-resolution'             => \$max_resolution,
    '--max-Fhkl-number'            => \$max_number_print_of_F,
    '--dump-cell-parameters'       => sub { $dump_cell_parameters = 1 },
    '--dump-cell-xyz-coordinates'  => sub { $dump_cell_xyz_coordinates = 1 },
    '--isotropic-Tf'               => sub { $iso_temperature_factor = 1 },
    '--dump-sorted-F'              => sub { $dump_descending_sort_Fhkl = 1 },
    '--dump-atoms-and-neighbors'   => sub { $dump_atoms_and_neighbors = 1 },
    '--external_CR_table'          => sub { $external_CR_table = 1 },
    '--Miller-indexes'             => \$Miller_indexes,
    '--get-hkl-data'               => \$hkl_file,
    '--dump-test-Fhkl'             => sub { $dump_test_Fhkl = 1 },
    '--use-perl-parser'            => sub{ $use_parser = 'perl' },
    '--use-c-parser'               => sub{ $use_parser = 'c' },
    '--options'                    => sub { options; exit },
    '--help,--usage'               => sub { usage; exit },
    '--version'                    => sub { print 'cod-tools version ',
                                            $COD::ToolsVersion::Version, "\n";
                                            exit }
);

binmode STDOUT, ':encoding(UTF-8)';
binmode STDERR, ':encoding(UTF-8)';

# tests for options
# --provide-Miller-indexes
if(defined $Miller_indexes) {
    foreach( split /\s/, $Miller_indexes ) {
        ##  print "$_\n";
        if ( $_!~/\d/ ) {
            report_message( {
                'program'   => $0,
                'err_level' => 'ERROR',
                'message'   => 'the option \'--provide-Miller-indexes\' '
                             . 'contains incorrect argument'
            }, $die_on_error_level->{'ERROR'} );
        }
    }
}

@ARGV = ( '-' ) unless @ARGV;

my $hkl_and_F_sorted;
for my $filename (@ARGV) {

    my $options = { 'parser' => $use_parser, 'no_print' => 1 };
    my ( $data, $err_count, $messages ) = parse_cif( $filename, $options );
    process_parser_messages( $messages, $die_on_error_level );

    if( !@{$data} || !defined $data->[0] || !defined $data->[0]{name} ) {
        report_message( {
            'program'   => $0,
            'filename'  => $filename,
            'err_level' => 'WARNING',
            'message'   => 'file seems to be empty'
        }, $die_on_error_level->{'WARNING'} );
        next;
    }

    for my $datablock (@{$data}) {
        my $dataname = 'data_' . $datablock->{name} if defined $datablock->{name};
        my $values = $datablock->{'values'};

        local $SIG{__WARN__} =  sub { process_warnings( {
                                       'message'       => @_,
                                       'program'       => $0,
                                       'filename'      => $filename,
                                       'add_pos'       => $dataname
                                      }, $die_on_error_level ) };

        my @hkl_and_F;
        eval {
        ## my $radiation_wavelengths = remove_su(
        ##     $datablock->{values}{_diffrn_radiation_wavelength} );
        my $cell_parameters = get_unit_cell_parameters( $values );
        my $f2o = symop_ortho_from_fract(
            @{$cell_parameters->{crystal_cell_degrees}} );
        my $atoms = atoms_from_cif( $values, $f2o );

        my %atom_index; # contains a index in $atoms using 'label' key.
        foreach (@{$atoms}) {
            $atom_index{$_->{'site_label'}} = $_->{'index'};
        }

        #---------------------------------------------------------
        tests_outputs( $atoms, $filename, $cell_parameters );
        #---------------------------------------------------------

            # Test of aniso coefficients: if not all atoms (except H and D)
            # contains aniso coef. then isotrophic koef. are used.
        if( !$iso_temperature_factor ) {
            foreach my $atom (@{$atoms}) {
                if( !defined $atom->{aniso_value_Uij}[0] &&
                    $atom->{'site_label'} !~ /[H|D]\d++/ ) {
                    warn 'WARNING, the CIF file does not contain the full '
                       . 'data of the standard anisotropic atomic displacement '
                       . 'components' . "\n";
                    if( $atom->{U_value_of_B_factor} !~ /\d\.\d++/ &&
                        $atom->{B_factor} !~ /\d\.\d++/) {
                        die 'ERROR, the CIF does not contain the correct '
                          . 'values for temperature factor calculation' . "\n";
                        }
                    $iso_temperature_factor = 1;
                    last;
                }
            }
        }

        my $scat_dispersion_key = 0;
        if( defined $atoms->[0]{'scat_dispersion_real'} ) {
            $scat_dispersion_key = 1;
        }
        # seaching list of chemical elements;
        my $chemical_element_list = [];
        foreach my $atom (@{$atoms}) {
            if ( !grep { /$atom->{chemical_type}/ } @{$chemical_element_list} ) {
                push @{$chemical_element_list}, $atom->{chemical_type};
            }
        }
        # checking of the possible Cromer Mann coefficients;
        foreach my $atom (@{$atoms}) {
            if( $external_CR_table ||
                !defined $atom->{Crommer_Mann_coefficients}[0] ) {
                # external source
                my $CromerManncoeff =
                    $COD::CromerMann::atoms{ $atom->{chemical_type} };
                #-----------------------------------------------------
                # print out of Cromer Mann coef. from external sources;
                if( !defined $atom->{Crommer_Mann_coefficients}[0] ) {
                    my $del_i;
                    for my $i (0..$#{$chemical_element_list}) {
                        if( $chemical_element_list->[$i] eq
                            $atom->{chemical_type} ) {
                            print "External CR $atom->{chemical_type}: ";
                            print "@{$CromerManncoeff} \n";
                            $del_i = $i;
                        }
                    }
                    if( defined $del_i) {
                        splice @{$chemical_element_list}, $del_i, 1
                    };
                }
                #----------------------------------------------------
                ## print "@{$CromerManncoeff}\n";
                if( !defined $CromerManncoeff->[0] ) {
                    die 'ERROR, the Cromer-Mann coefficients are not '
                      . 'defined external source/CIF file for element '
                      . "'$atom->{chemical_type}'\n";
                }
            }
        }

        ## print "@{$chemical_element_list}\n";
        unless( $dump_Cromer_Mann_parameters || $dump_cell_parameters ||
                $dump_xyz_coordinates || $dump_cell_xyz_coordinates ) {

            my( $h_max, $h_min, $k_max, $k_min, $l_max, $l_min );
            if(!defined $Miller_indexes) {
                ( $h_max, $h_min, $k_max, $k_min, $l_max, $l_min ) =
                    @hkl_default;
            } else {
                ( $h_max, $h_min, $k_max, $k_min, $l_max, $l_min ) =
                    split /\s/, $Miller_indexes
            }

            my @abc_star = @{$cell_parameters->{reciprocal_cell_radians}}[0..2];

            foreach my $h_limit ( $h_min..$h_max ) {
            foreach my $k_limit ( $k_min..$k_max ) {
            foreach my $l_limit ( $l_min..$l_max ) {
                my @hkl = ($h_limit, $k_limit, $l_limit);
                next if( $h_limit == 0 && $k_limit == 0 && $l_limit == 0 );
                my $resolution_hkl = spacing_d_hkl(
                    @hkl,
                    @{$cell_parameters->{reciprocal_cell_radians}} );
                next if($resolution_hkl < $max_resolution);

                my ( $F_cos_comp_w_Tf_and_cor, $F_sin_comp_w_Tf_and_cor,
                     $F_cos_comp_w_cor, $F_sin_comp_w_cor,
                     $F_cos_comp, $F_sin_comp ) = (0,0,0,0,0,0);

                foreach my $atom (@{$atoms}) {
                    #------------------------------------------------------
                    my $CromerManncoeff;
                    if( $external_CR_table ||
                        !defined $atom->{Crommer_Mann_coefficients}[0] ) {
                        # external source
                        $CromerManncoeff =
                            $COD::CromerMann::atoms{ $atom->{chemical_type} };
                    } else { # from CIF file or external if does not defined;
                        $CromerManncoeff =
                            $atom->{Crommer_Mann_coefficients};
                    }
                    #------------------------------------------------------

                    my $atom_structure_factor =
                        atom_structure_factor_from_coefficients(
                            $CromerManncoeff, $resolution_hkl );

                    my $atom_structure_factor_corrected;
                    if( $scat_dispersion_key ) {
                        $atom_structure_factor_corrected =
                            sqrt( ( $atom_structure_factor +
                                    $atom->{'scat_dispersion_real'} )**2 +
                                    $atom->{'scat_dispersion_real'}**2 );
                    }

                    foreach my $sym_xyz (@{$atom->{symmetrical_xyz_fract}}) {
                        my $atom_hkl_phase = atom_phase_hkl_rad( @hkl, @{$sym_xyz} );

                        my $temperature_factor;
                        if( !$iso_temperature_factor ) {
                        # calculation of aniso temperature factor;
                        # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                            if( $atom->{chemical_type} eq 'H' ) {
                                if( defined $atom->{neighbors}[0] ) {
                                    my $H_neighbor_label = $atom->{neighbors}[0];
                                    my $H_neighbor_index = $atom_index{$H_neighbor_label};
                                    ## print "$H_neighbor_index $H_neighbor_label\n";
                                    $temperature_factor =
                                        temperature_factor_aniso(
                                            @{$atoms->[$H_neighbor_index]->{aniso_value_Uij}},
                                            @hkl, @abc_star );
                                    ## print "$temperature_factor\n";
                                } else {
                                    $temperature_factor = 1;
                                }
                            } else {
                                $temperature_factor =
                                    temperature_factor_aniso(
                                        @{$atom->{aniso_value_Uij}},
                                        @hkl, @abc_star );
                            }
                        } else { # calculation of iso temperature factor;
                            $temperature_factor =
                                exp( ( -$atom->{B_factor}/4 ) *
                                     ( 1/$resolution_hkl )**2 );
                        }

                        $F_cos_comp_w_Tf_and_cor +=
                            $atom->{atom_site_occupancy} *
                            $atom_structure_factor * cos( $atom_hkl_phase );
                        $F_sin_comp_w_Tf_and_cor +=
                            $atom->{atom_site_occupancy} *
                            $atom_structure_factor * sin( $atom_hkl_phase );
                        # structure factor components without corrections;
                        $F_cos_comp_w_cor +=
                            $atom->{atom_site_occupancy} * $atom_structure_factor *
                            $temperature_factor * cos( $atom_hkl_phase );
                        $F_sin_comp_w_cor +=
                            $atom->{atom_site_occupancy} * $atom_structure_factor *
                            $temperature_factor * sin( $atom_hkl_phase );
                        if( $scat_dispersion_key ) {
                            $F_cos_comp +=
                                $atom->{atom_site_occupancy} *
                                $atom_structure_factor_corrected *
                                $temperature_factor * cos( $atom_hkl_phase );
                             $F_sin_comp +=
                                $atom->{atom_site_occupancy} *
                                $atom_structure_factor_corrected *
                                $temperature_factor * sin( $atom_hkl_phase );
                        } else {
                            die 'ERROR, the CIF file does not contain the data '
                              . 'of anomalous-dispersion scattering factor'
                              . "\n";
                        }
                    } # foreach of cell atoms;
                } # foreach of assimetric unit atoms;

                my $F_hkl_without_Tf_and_cor =
                    sqrt( $F_cos_comp_w_Tf_and_cor**2 +
                          $F_sin_comp_w_Tf_and_cor**2 );
                my $F_hkl_iso_without_cor =
                    sqrt( $F_cos_comp_w_cor**2 + $F_sin_comp_w_cor**2 );
                my $F_hkl_sguared =
                    $F_cos_comp**2 + $F_sin_comp**2;

                if( $dump_test_Fhkl ) {
                    printf "%3s %3s %3s %0.3f %0.3f %0.3f\n",
                        @hkl,
                        $F_hkl_without_Tf_and_cor**2,
                        $F_hkl_iso_without_cor**2,
                        $F_hkl_sguared;
                }

                push( @hkl_and_F, [ $F_hkl_sguared ,\@hkl ] );

            }}} # end for
            exit if( $dump_test_Fhkl );
        } # end unless

        $hkl_and_F_sorted = sort_Fhkl( \@hkl_and_F );
        # ----------------------------------------------------
        if( $dump_descending_sort_Fhkl ) {
            foreach (@{$hkl_and_F_sorted}) {
                printf "%3s %3s %3s %0.3f\n",
                    @{$_->[1]}, $_->[0];
            }
            exit;
        }

        if( 1 ) {
            my $search_equal_intensities = sort_Friedel( $hkl_and_F_sorted );
            if( defined $max_number_print_of_F ) {
                if( ($max_number_print_of_F - 1) >
                    $#{$search_equal_intensities} ) {
                    warn 'WARNING, the value of --max-Fhkl-number is large'
                       . 'than the number of calculated structure factors -- '
                       . "printing out of full structure factors number\n";
                } else {
                    $search_equal_intensities =
                        [ @{$search_equal_intensities}[0..($max_number_print_of_F - 1)] ];
                }
            }
            if( !defined $hkl_file ) {
                foreach my $Fri_Fhkl (@{$search_equal_intensities}) {
                    my $adding_field = (' %3s %3s %3s')x$#{$Fri_Fhkl};
                    printf '%0.3f'.$adding_field."\n",
                    $Fri_Fhkl->[0], map{ @{$_} } @{$Fri_Fhkl}[1..$#{$Fri_Fhkl}];
                }
            }
        }
        }; # end eval
        if ($@) {
            process_errors( {
              'message'       => $@,
              'program'       => $0,
              'filename'      => $filename,
              'add_pos'       => $dataname
            }, $die_on_error_level->{'ERROR'} )
        };
    } # end foreach data block
} # end foreach file

# for hkl data extracting from CIF.hkl and comparison with my calculated
# values;
if( defined $hkl_file ) {
    my $hkl_options = { 'parser' => $use_parser, 'no_print' => 1 };
    my ( $hkl_data, $hkl_err_count, $messages ) = parse_cif( $hkl_file,
                                                             $hkl_options );
    process_parser_messages( $messages, $die_on_error_level );
    next if ( $hkl_err_count > 0 );

    # take a top segment of calculated and sorted structure factors;
    if( defined $max_number_print_of_F ) {
        if( ($max_number_print_of_F - 1) > $#{$hkl_and_F_sorted} ) {
            report_message( {
                'program'   => $0,
                'filename'  => $hkl_file,
                'err_level' => 'WARNING',
                'message'   => 'the value of --max-Fhkl-number is greater '
                             . 'than the number of calculated structure '
                             . 'factors -- printing out of full structure '
                             . 'factors number'
            }, $die_on_error_level->{'WARNING'} );
        } else {
            $hkl_and_F_sorted =
                [ @{$hkl_and_F_sorted}[0..($max_number_print_of_F - 1)] ];
        }
    }

    for my $hkl_datablock (@{$hkl_data}) {
        my $values = $hkl_datablock->{'values'};
        my( $h_values, $k_values, $l_values,
            $F_sguared_mes,
            $F_squared_calc,
            $F_squared_sigma_mes ) =
                ( $values->{_refln_index_h},
                  $values->{_refln_index_k},
                  $values->{_refln_index_l},
                  $values->{_refln_f_squared_meas},
                  $values->{_refln_f_squared_calc},
                  $values->{_refln_f_squared_sigma} );

        foreach my $calculated (@{$hkl_and_F_sorted}) {
            my $set_flag = 0;
            for my $i (0..$#{$values->{_refln_index_h}}) {
                my $condition =
                    ( $calculated->[1][0] == $h_values->[$i] &&
                      $calculated->[1][1] == $k_values->[$i] &&
                      $calculated->[1][2] == $l_values->[$i] );
                if( $condition ) {
                    if(
                        ($calculated->[0] >
                         ($F_sguared_mes->[$i] - $F_squared_sigma_mes->[$i])) &&
                        ($calculated->[0] <
                         ($F_sguared_mes->[$i] + $F_squared_sigma_mes->[$i]))
                        ) {
                        print 'Y ';
                    } else {
                        print 'N ';
                    };
                    my $error_squared =
                        ( abs( $F_sguared_mes->[$i] - $calculated->[0] ) /
                          $F_sguared_mes->[$i] ) * 100 ;
                    print  "My Calc: $calculated->[0] "
                         . "Mes: $F_sguared_mes->[$i] "
                         . "Their Calc: $F_squared_calc->[$i] "
                         . "Sigma: $F_squared_sigma_mes->[$i] "
                         . " My Error: $error_squared\n";
                    $set_flag = 1;
                }
                last if( $set_flag );
            } # end for
        } # end foreach
    } # end for data blocks
}

#-------------------------------------------------------------------#
# Remove the standard deviation (standard uncertainties) components
# from input data; 
# Parameters: reference to an array with scalar
# values; for ex.: 0.21582(8) 
# Return: reference to an array without
# std. deviations; for ex.: 0.21582
sub remove_su
{
    my( $input ) = @_;
    return [ map { my $value = $_;
                   $value =~ s/\(\d+\)//; $value } @{$input} ];
}

#--------------------------------------------------------------------#
# Function of extracting atom information from the CIF file.
#
# Parameters:
#   $values
#               A reference to an array of hashes where the data from the CIF
#               file is stored.
#   $f2o
#               A refence to the orthogonalization matrix for the calculation
#               of orthogonal coordinates.
#
# Returns:
# $atoms = [
#           # [0]
#           {
#               site_label => 'O10',
#               chemical_type => 'O',
#               coordinates_fract" => [0.1, 0.2, 0.3],
#               coordinates_ortho => [10, 20.5, 25.2],
#               atom_site_occupancy => 0.5,
#               B_factor => 0.78,
#               U_value_of_B_factor => 0.03,
#               aniso_value_Uij => [0.01, 0.002, 0.003,..,0.006] # 6 values,
#               Crommer_Mann_coefficients => [1.10, .., .., ] # 9 values;
# a1, a2, .., b1, b2.., b4, c.
#               symmetrical_xyz_fract => [\@xyz1, \@xyz2, .. ],
#               symmetrical_xyz_ortho => [\@xyz1, \@xyz2, .. ],
#               count_of_cell_atoms => 10, # only for first atom;
#               neighbors => [C23, C25],
#           },
#           # [1]
#           {
#             ...
#           },
#  ];
sub atoms_from_cif
{
    my( $values, $f2o ) = @_;

    my $Cromer_Mann_coefficients = get_Cromer_Mann_coefficients( $values );
    my $themal_coefficients = get_thermal( $values );
    my $sym_atoms_and_their_count = cell_filling_sym( $values, $f2o );
    my $atoms_neighbors = get_neighbors( $values );

    my $atoms = atom_array_from_cif( { 'values' => $values }, {} );

    foreach my $atom ( @{$atoms} ) {
        my $index = $atom->{'index'};
        %{$atom} = (%{$atom}, %{$Cromer_Mann_coefficients->[$index]} );
        %{$atom} = (%{$atom}, %{$themal_coefficients->[$index]});
        %{$atom} = (%{$atom}, %{$sym_atoms_and_their_count->[$index]});
        %{$atom} = (%{$atom}, %{$atoms_neighbors->[$index]});
    }

    return $atoms;
}

#--------------------------------------------------------------------#
# Function of extracting atoms Crommer-Mann coefficients the CIF file.
#
# Parameters:
#    values - a reference to array of hashes where a data from the CIF
#    file is stored
#
# Returns:
# \@ = (
#           # [0]
#           {
#               Crommer_Mann_coefficients => [1.10, .., .., ] # 9 values;
# a1, a2, .., b1, b2.., b4, c.
#           },
#           # [1]
#           {
#               Crommer_Mann_coefficients => [1.10, .., .., ]
#           },
#  );
sub get_Cromer_Mann_coefficients
{
    my( $values ) = @_;

    my @atoms_aniso_coefficients;

    my $labels = $values->{'_atom_site_label'};
    my $chemical_type_of_Cromer_Mann_loop = [];
    # filling data by empty hashes if label _atom_type_symbol
    # is not defined;
    if( !defined $values->{'_atom_type_symbol'}[0] ) {
        for(0..$#{$labels}) {
            push( @atoms_aniso_coefficients, {} );
        }
    }
    # correction of chemical element to standard record;
    foreach (@{$values->{_atom_type_symbol}}) {
        my $double_sym = ucfirst( lc( substr( $_,0,2 )));
        $double_sym =~ s/\s//g;
        push( @{$chemical_type_of_Cromer_Mann_loop}, $double_sym );
    }
    my $Cromer_Mann_coefficients =
        [ $values->{'_atom_type_scat_cromer_mann_a1'},
          $values->{'_atom_type_scat_cromer_mann_a2'},
          $values->{'_atom_type_scat_cromer_mann_a3'},
          $values->{'_atom_type_scat_cromer_mann_a4'},
          $values->{'_atom_type_scat_cromer_mann_b1'},
          $values->{'_atom_type_scat_cromer_mann_b2'},
          $values->{'_atom_type_scat_cromer_mann_b3'},
          $values->{'_atom_type_scat_cromer_mann_b4'},
          $values->{'_atom_type_scat_cromer_mann_c'} ];
    my $atom_scat_dispersion_real =
          $values->{'_atom_type_scat_dispersion_real'};
    my $atom_scat_dispersion_imag =
          $values->{'_atom_type_scat_dispersion_imag'};
    for my $i (0..$#{$labels}) {
        # general loop
        my $atom_chemical_type = $values->{'_atom_site_type_symbol'}[$i];
        if( !defined $atom_chemical_type ) {
            $atom_chemical_type = ucfirst( lc( substr($labels->[$i],0,2)));
            $atom_chemical_type =~ s/\d//;
        }

        for my $y (0..$#{$chemical_type_of_Cromer_Mann_loop}) {
        ## print length($chemical_type_of_Cromer_Mann_loop->[$y]), 
        ## ">>> $chemical_type_of_Cromer_Mann_loop->[$y]\n";
        ## print length($atom_chemical_type), 
        ## "<<< $atom_chemical_type\n";
            if( $atom_chemical_type eq
                 $chemical_type_of_Cromer_Mann_loop->[$y] ) {
                my %thermal = (
                    'Crommer_Mann_coefficients' => [
                        $Cromer_Mann_coefficients->[0][$y],
                        $Cromer_Mann_coefficients->[1][$y],
                        $Cromer_Mann_coefficients->[2][$y],
                        $Cromer_Mann_coefficients->[3][$y],
                        $Cromer_Mann_coefficients->[4][$y],
                        $Cromer_Mann_coefficients->[5][$y],
                        $Cromer_Mann_coefficients->[6][$y],
                        $Cromer_Mann_coefficients->[7][$y],
                        $Cromer_Mann_coefficients->[8][$y] ],
                    'scat_dispersion_real' => $atom_scat_dispersion_real->[$y],
                    'scat_dispersion_imag' => $atom_scat_dispersion_imag->[$y]
                    );
                push @atoms_aniso_coefficients, \%thermal;
            } # end of if;
        } # second for
    } # first for 
    return \@atoms_aniso_coefficients;
}

#--------------------------------------------------------------------#
# Function of extracting atoms anisotropic and isotropic 
# thermal parameteres the CIF file.
#
# Parameters:
#    values - a reference to array of hashes where a data from the CIF
#    file is stored
#
# Returns:
# \@ = (
#           # [0]
#           {
#               B_factor => 0.78,
#               U_value_of_B_factor => 0.03,
#               aniso_value_Uij => [0.01, 0.002, 0.003,..,0.006] # 6 values,
#           },
#           # [1]
#           {
#               B_factor => 0.78,
#               U_value_of_B_factor => 0.03,
#               aniso_value_Uij => [0.01, 0.002, 0.003,..,0.006] # 6 values,
#           },
#  );
sub get_thermal
{
    my( $values ) = @_;

    my @atoms_thermal_coefficients;

    my $thermal_parameter_B =
        remove_su( $values->{'_atom_site_b_iso_or_equiv'} );
    my $squared_displacement_U =
        remove_su( $values->{'_atom_site_u_iso_or_equiv'} );
    my @anisotrophic_U_ij_values =
        ( remove_su( $values->{'_atom_site_aniso_u_11'} ),
          remove_su( $values->{'_atom_site_aniso_u_22'} ),
          remove_su( $values->{'_atom_site_aniso_u_33'} ),
          remove_su( $values->{'_atom_site_aniso_u_23'} ),
          remove_su( $values->{'_atom_site_aniso_u_13'} ),
          remove_su( $values->{'_atom_site_aniso_u_12'} ) );
    # _atom_site_aniso_label needs for extracting of anisotrophic values; 
    my %temporary_aniso_data;
    for my $i (0..$#{$values->{_atom_site_aniso_label}}) {
        my $atom_aniso_label =
            $values->{'_atom_site_aniso_label'}[$i];
        $temporary_aniso_data{$atom_aniso_label} =
            [ $anisotrophic_U_ij_values[0]->[$i],
              $anisotrophic_U_ij_values[1]->[$i],
              $anisotrophic_U_ij_values[2]->[$i],
              $anisotrophic_U_ij_values[3]->[$i],
              $anisotrophic_U_ij_values[4]->[$i],
              $anisotrophic_U_ij_values[5]->[$i] ];
    }

    for my $i (0..$#{$values->{_atom_site_label}}) {
        my $atom_label = $values->{'_atom_site_label'}[$i];
        my %thermal_info = (
            'B_factor' => ($thermal_parameter_B->[$i] ||
                         $squared_displacement_U->[$i] !~ /\d\.\d++/ ?
                         $thermal_parameter_B->[$i] :
                         8 * ($PI**2) * $squared_displacement_U->[$i]
            ),
            'U_value_of_B_factor' => $squared_displacement_U->[$i],
            'aniso_value_Uij'     => $temporary_aniso_data{$atom_label}
            );
        push( @atoms_thermal_coefficients, \%thermal_info );
    }
    return \@atoms_thermal_coefficients;
}

#--------------------------------------------------------------------#
# Function of extracting unit-cell parameters of the CIF file.
#
# Parameters:
#    values - a reference to array of hashes where a data from the CIF
#    file is stored
#
# Return
# $a = {
#        crystal_cell_degrees => [a, b, c, alpha, beta, gamma ],
#        crystal_cell_radians => [a, b, c, alpha_rad, beta_rad, gamma_rad ],
#        cell_volume => 14.055,
#        reciprocal_cell_degrees => [a*, b*, c*, alpha*, beta*, gamma*],
#        reciprocal_cell_radians => [a*,b*,c*,alpha_rad*,beta_rad*,gamma_rad*],
#      }
sub get_unit_cell_parameters
{
    my( $values ) = @_;

    my @crystal_lattice = get_cell( $values );
    my $cell_volume = ${remove_su( $values->{_cell_volume} )}[0];
    my( $a, $b, $c, $alpha, $beta, $gamma ) = @crystal_lattice;
    my @crystal_cell_radians =
        ( $a, $b, $c, deg2rad( $alpha, $beta, $gamma ));
    # alpha between b and c; beta between a and c; gamma between a and b.
    # @star_crystal_lattice = ( a*[0], b*[1], c*[2], alpha*, beta*, gamma*[5] );
    my @abc_star_values = (
        d2r_length( $b, $c, deg2rad( $alpha ), $cell_volume ), # a*
        d2r_length( $a, $c, deg2rad( $beta ), $cell_volume ), # b*
        d2r_length( $a, $b, deg2rad( $gamma ), $cell_volume )  # c*
        );
    my @alpha_beta_gamma_star_radians = (
        d2r_angle( deg2rad( $beta, $gamma, $alpha ) ), # alpha*
        d2r_angle( deg2rad( $alpha, $gamma, $beta ) ),  # beta*
        d2r_angle( deg2rad( $alpha, $beta, $gamma ) ) );  # gamma*
    my  @alpha_beta_gamma_star_degrees =
        rad2deg( @alpha_beta_gamma_star_radians );
    my @star_crystal_cell_parameters_degrees =
        (@abc_star_values, @alpha_beta_gamma_star_degrees);
    my @star_crystal_cell_parameters_radians =
        (@abc_star_values, @alpha_beta_gamma_star_radians);
    my $crystal_par = {
        crystal_cell_degrees => \@crystal_lattice,
        crystal_cell_radians => \@crystal_cell_radians,
        cell_volume => $cell_volume,
        reciprocal_cell_degrees => \@star_crystal_cell_parameters_degrees,
        reciprocal_cell_radians => \@star_crystal_cell_parameters_radians
    };
    return $crystal_par;
}

#--------------------------------------------------------------------#
# Function of converting lattice parameters (length of cell edges):
# from direct space to reciprocal.
#
# Parameters:
# for example  c* = d2r_length( a, b, angle(a,b), cell_volume );
# Return:
# for example 0.554 [angstrom**-1]
sub d2r_length
{
    my( $c_length1, $c_length2, $angle1_2, $cell_vol ) = @_;
    return ($c_length1 * $c_length2 * sin( $angle1_2 )) / $cell_vol;
}

#--------------------------------------------------------------------#
# Function of converting lattice parameters (cell angles):
# from direct space to reciprocal.
#
# Parameters and return:
# gamma* = d2r_angle( alpha, beta, GAMMA );
# beta* = d2r_angle( alpha, gamma, BETA );
# alpha* = d2r_angle( beta, gamma, ALPHA );
sub d2r_angle
{
    my( $angle1, $angle2, $angle_which_converts ) = @_;
    use POSIX;
    return POSIX::acos(
        (cos( $angle1 ) * cos( $angle2 ) -
         cos( $angle_which_converts)) /
        (sin( $angle1 ) * sin( $angle2 )) );
}

#----------------------------------------------------------------------#
# Function for calculation the distance(resolution) between hkl layers.
#
# Parameters:
# hkl (index of reflection), reciprocal lattice.
# for ex.: spacing_d_hkl( h, k, l, a*, b*, c*, alpha*, beta*, gamma* )
# Return
# hkl resolution (in angstrom): 5. 
sub spacing_d_hkl
{
    my( $h, $k, $l, $a_star, $b_star, $c_star,
         $alpha_star, $beta_star, $gamma_star ) = @_;
    return 1 /
    sqrt( $h**2 * $a_star**2 +
              $k**2 * $b_star**2 +
              $l**2 * $c_star**2 +
              2 * $h * $k * $a_star * $b_star * cos( $gamma_star ) +
              2 * $h * $l * $a_star * $c_star * cos( $beta_star ) +
              2 * $k * $l * $b_star * $c_star * cos( $alpha_star ) );
}


#----------------------------------------------------------------------#
# Function converts degree to radian.
# deg2rad( 180 ) = Pi;
# deg2rad( angle1(deg), angle2(deg), .. ) = ( angle1(rad) , angle2(rad), .. );
sub deg2rad
{
    return map { ($_ * $PI) / 180 } @_;
}

#----------------------------------------------------------------------#
# Function converts radian to degree.
# rad2deg( Pi ) = 180;
# rad2deg( angle1(rad), angle2(rad), .. ) = ( angle1(deg) , angle2(deg), .. );
sub rad2deg
{
    return map { ($_ * 180) / $PI } @_;
}

#----------------------------------------------------------------------#
# Print output of option --dump-cell-parameters.
sub printing_of_dump_cell_parameters
{
    my( $file_name, $cell_parameters ) = @_;
    print "$file_name\n";
    printf "%15s %0.5f %0.5f %0.5f %0.5f %0.5f %0.5f\n",
    'Direct cell     (deg)', @{$cell_parameters->{crystal_cell_degrees}};
    printf "%15s %0.5f %0.5f %0.5f %0.5f %0.5f %0.5f\n",
    'Direct cell     (rad)', @{$cell_parameters->{crystal_cell_radians}};
    printf "%15s %0.5f %0.5f %0.5f %0.5f %0.5f %0.5f\n",
    'Reciprocal cell (deg)', @{$cell_parameters->{reciprocal_cell_degrees}};
    printf "%15s %0.5f %0.5f %0.5f %0.5f %0.5f %0.5f\n",
    'Reciprocal cell (rad)', @{$cell_parameters->{reciprocal_cell_radians}};
    printf "%15s %0.5f\n",
    'Volume         ', $cell_parameters->{cell_volume};

    return;
}

#----------------------------------------------------------------------#
# Function for atom structure factor calculation.
#
# Parameters:
# [ Cromer_Mann coefficients: a1, .., a4, b1, .., b4, c ], diff. wavelength,
# sin(teta_hkl);
#
# Return:
# Value of atom structure factor which depends on wavelength and sin(teta_hkl)
# for ex.:
# atom_structure_factor_from_coefficient([a1,..,c],1.54, 0.33) = 1.4;
sub atom_structure_factor_from_coefficients
{
    my( $Cromer_Mann, $resolution ) = @_;
    my( $a1, $a2, $a3, $a4, $b1, $b2, $b3, $b4, $c ) = @{$Cromer_Mann};
    my $sin_teta_div_wavelength = 1 / (2 * $resolution);
    return
    $a1 * exp( -$b1 * $sin_teta_div_wavelength**2 ) +
    $a2 * exp( -$b2 * $sin_teta_div_wavelength**2 ) +
    $a3 * exp( -$b3 * $sin_teta_div_wavelength**2 ) +
    $a4 * exp( -$b4 * $sin_teta_div_wavelength**2 ) +
    $c;
}

#----------------------------------------------------------------------#
# Function for calculation of atom hkl_phase. 
#
# Parameters: 
# phase_hkl( @hkl, @xyz_fract, @cell_length_abc);
# Return:
# phase(in radians)
# for ex.: 
# phase_hkl( @hkl, @xyz_fract, @cell_length_abc) = 1.12;
sub atom_phase_hkl_rad
{
    my( $h, $k, $l, $fract_x, $fract_y, $fract_z ) = @_;
    return 2 * $PI * ($h * $fract_x + $k * $fract_y +
                       $l * $fract_z);
}

# Function for temperature factor calculation (anisotropic);
# equation from "Principles of protein X-ray crystallography",
# Jan Drenth, page 94; T(aniso; hkl)=..
# 
# Parameters:
# temperature_factor_aniso( U11, U22, U33, U23, U13, U12, h, k, l, a*, b*, c* );
# Return:
# T(aniso, hkl).
sub temperature_factor_aniso
{
    my( $U_11, $U_22, $U_33, $U_23, $U_13, $U_12,
        $h, $k, $l, $repro_length_a, $repro_length_b, $repro_length_c ) = @_;
    return exp( (-2 * $PI**2) *
                ($U_11 * ($h**2) * ($repro_length_a**2) +
                 $U_22 * ($k**2) * ($repro_length_b**2) +
                 $U_33 * ($l**2) * ($repro_length_c**2) +
                 2 * $U_12 * $h * $k * $repro_length_a * $repro_length_b +
                 2 * $U_13 * $h * $l * $repro_length_a * $repro_length_c +
                 2 * $U_23 * $k * $l * $repro_length_b * $repro_length_c ) );
}

# Function for symmetric transformations of atom positions for receiving of 
# symmetrical atoms.
#
# Parameters: 
# 1. values - a reference to array of hashes where a data from the CIF
# file is stored; 2. refence to orthogonalization matrix
# for orthogonal coordinates calculation.
# f.e.: cell_filling_sym( \[\%1, \%2, ..], "0022254", \matrix );
#
# Return: 
# \@sym_atom_info = (
#           # [0]
#           {
#               symmetrical_xyz_fract => [\@xyz1, \@xyz2, .. ],
#               symmetrical_xyz_ortho => [\@xyz1, \@xyz2, .. ],
#               
#            }, 
#        .. )
sub cell_filling_sym
{
    my( $values, $f2o ) = @_;

    my @xyz_arrays =
        ( remove_su( $values->{'_atom_site_fract_x'} ),
          remove_su( $values->{'_atom_site_fract_y'} ),
          remove_su( $values->{'_atom_site_fract_z'} ) );

    my $sym_data = get_symmetry_operators( { 'values' => $values } );

    my @sym_operators = map { symop_from_string($_) } @{$sym_data};

    my @sym_atom_info;
    foreach my $i (0..$#{$values->{_atom_site_label}}) {
        my %sym_atom_coordinates;

        my $fract_xyz = [ $xyz_arrays[0]->[$i],
                          $xyz_arrays[1]->[$i],
                          $xyz_arrays[2]->[$i] ];
        my $ortho_xyz = symop_vector_mul( $f2o, $fract_xyz );

        foreach my $symop ( @sym_operators ) {
            my $new_xyz_fract = symop_apply( [@{$fract_xyz}, 1], $symop);
            $new_xyz_fract = [ @{$new_xyz_fract}[0..2] ];
            my $new_xyz_ortho = symop_vector_mul( $f2o, $new_xyz_fract );

            push @{$sym_atom_coordinates{'symmetrical_xyz_fract'}},
                                                        $new_xyz_fract;
            push @{$sym_atom_coordinates{'symmetrical_xyz_ortho'}},
                                                        $new_xyz_ortho;
        }
        push @sym_atom_info, \%sym_atom_coordinates;
    }

    # searching of symmetric atom twins;
    my @temporary_sym_all;
    foreach my $ref (@sym_atom_info) {
        my $sym_atoms_ortho = $ref->{'symmetrical_xyz_ortho'};
        ## print "$sym_atoms_ortho\n";
        my $sym_atoms_whitout_twins = [];
        my $length_of_ref = $#{$sym_atoms_ortho};
        for my $i (0..$length_of_ref) { # symmetric atoms
            for my $k ($i+1..$length_of_ref) {
                my $what_distance = distance( $sym_atoms_ortho->[$i],
                                              $sym_atoms_ortho->[$k] );
                if( $what_distance < 0.01 ) {
                    unless( compare_number($sym_atoms_whitout_twins, $k) ) {
                        push @{$sym_atoms_whitout_twins}, $k;
                    }  # end if unless
                } # end if
            } # for 3
        } # for 2
        push @temporary_sym_all, $sym_atoms_whitout_twins;
        ## print "@{$sym_atoms_whitout_twins} $sym_atoms_whitout_twins\n";
    } # for 1

    # deleting of symmetic atom twins;
    for my $i (0..$#temporary_sym_all) {
        if( defined $temporary_sym_all[$i]->[0] ) {
            my @number_sort_reverse = sort {$b <=> $a} @{$temporary_sym_all[$i]};
            foreach my $del_position ( @number_sort_reverse  ) {
                ## print "$del_position\n";
                ## print @{$sym_atom_info[$i]->{symmetrical_xyz_ortho}},">>>\n";
                splice @{$sym_atom_info[$i]->{symmetrical_xyz_ortho}},
                       $del_position, 1;
                splice @{$sym_atom_info[$i]->{symmetrical_xyz_fract}},
                        $del_position, 1;
            }
        } # end if
    }

    # only for first element adding the atoms full count of crystal cell;
    my $atoms_number_of_cell = 0;
    foreach my $ref (@sym_atom_info) {
        $atoms_number_of_cell += scalar @{$ref->{symmetrical_xyz_ortho}};
    }
    $sym_atom_info[0]{'count_of_cell_atoms'} = $atoms_number_of_cell;

    return \@sym_atom_info;
}


#---------------------------------------------------------------
# Subroutine symop_apply() was copied from 'cif_fillcell' script. rev. 1440;
#===============================================================#
sub symop_apply
{
    my($atom_xyz, $symop) = @_;
    my @new_atom_xyz;

    for (my $i = 0; $i < @{$symop}; $i++) {
        $new_atom_xyz[$i] = 0;
        for(my $j = 0; $j < @{$symop}; $j++) {
            ${$atom_xyz}[$j] =~ s/\(\d+\)$//;
            $new_atom_xyz[$i] += ${$atom_xyz}[$j] * ${$symop}[$i][$j];
        }
        $new_atom_xyz[$i] = modulo_1($new_atom_xyz[$i]);
    }

    return \@new_atom_xyz;
}

#----------------------------------------------------------------------#
# Function which contains tests block;
#
# Parameters:
#    f.e. tests_outputs( 1, 2, 3), where
#    1. output of atoms_from_cif(); 2. name of file; 3.
#    output of get_unit_cell_parameters;
# 
# Return:
# Print outputs of tests.
sub tests_outputs
{
    my( $atoms, $file_name, $cell_parameters ) = @_;
    if( $dump_xyz_coordinates ) {
        print $#$atoms+1, "\n";
        print "$file_name\n";
            foreach my $atom (@{$atoms}) {
                printf "%2s %.5f %.5f %.5f\n", $atom->{chemical_type},
                @{$atom->{coordinates_ortho}};
            }
        exit;
    }
    if( $dump_Cromer_Mann_parameters ) {
        print "$file_name\n";
        foreach my $atom (@{$atoms}) {
            print $atom->{'site_label'} . " " . $atom->{'chemical_type'} . " ",
            $atom->{Crommer_Mann_coefficients}[0] ?
                "@{$atom->{Crommer_Mann_coefficients}}\n" : "Empty\n";
        }
        exit;
    }
    if( $dump_cell_parameters ) {
        printing_of_dump_cell_parameters( $file_name, $cell_parameters );
        exit;
    }
    if( $dump_cell_xyz_coordinates ) {
        print $atoms->[0]{count_of_cell_atoms}, "\n";
        print "$file_name\n";
        foreach my $atom (@{$atoms}) {
            foreach my $sym_xyz (@{$atom->{symmetrical_xyz_ortho}}) {
                printf "%2s %.5f %.5f %.5f\n", $atom->{chemical_type},
                @{$sym_xyz};
            }
        }
        exit;
    }
    if( $dump_atoms_and_neighbors ) {
        foreach (@{$atoms}) {
            local $, = ' ';
            print $_->{'site_label'},': ' , @{$_->{neighbors}}
                                                        ? @{$_->{neighbors}}
                                                        : 'NULL', "\n";
        }
        exit;
    }
}

#---------------------------------------------------------------------------
# Function
#
sub compare_number
{
    my( $reference_to_array, $template ) = @_;
    foreach my $number ( @{$reference_to_array} ) {
        return 1 if( $number == $template );
    }
    return 0;
}

#------------------------------------------------------------------------------
# Function for Fhkl descending sorting
#
# Parameters: 
# \@array = (
#           # [0]
#           [F_value1, [h1,k1,l1] ],
#           # [1]
#           [F_value2, [h2,k2,l2] ],
#
# Return:
# sorted \@array against F values;

sub sort_Fhkl
{
    my( $ref_to_Fhkl ) = @_;

    my $copied_ref_to_Fhkl = $ref_to_Fhkl;
    my @Fhkl_sorted;

    my $max_flag = 0; # control of 'until' running 
    # and keeping of Fmax value in Fhkl array;
    until( $max_flag ) {
        my $k = 0; # counter
        my $mak_k; # keep of Fmax index value in array; 
        foreach my $ref (@{$copied_ref_to_Fhkl}) {
            # print "$ref->[0]\n";
            if( $max_flag < $ref->[0] ) {
                $max_flag = $ref->[0];
                $mak_k = $k;
            }
            $k++;
        };
        ## print "array pos: $mak_k Factor value: $max_flag\n";
        push @Fhkl_sorted, $copied_ref_to_Fhkl->[$mak_k];
        splice @{$copied_ref_to_Fhkl}, $mak_k, 1; # deleting Fmax value from
        # array and searching of new Fmax;
        if( $#{$copied_ref_to_Fhkl} == 0 ) {
            $max_flag = 1; # stop 'until'  
        }  else {
            $max_flag = 0; # run  
        }
    } # end until;
    return \@Fhkl_sorted;
}

#------------------------------------------------------------------------------
# Function for Fhkl sorting against Friedel's lows;
#
# Parameters: 
# sort_Fhkl() outputs. 
#
# Return:
# \@array = (
#           # [0]
#           [F_value1, [h1,k1,l1], [h1,k1,l1], .. ],
#           # [1]
#           [F_value2, [h2,k2,l2], [h2,k2,l2], .. ]
sub sort_Friedel
{
    my( $ref_to_sorted_Fhkl ) = @_;
    my @output; # 
    my $cur_i = 0; # remember of $ref_to_sorted_Fhkl index value;
    my $adding_i = 0; # remember of @output index value;
    my $add = 1; # change $cur_i; 

    while( 1 ) {

        last if( !defined $ref_to_sorted_Fhkl->[$cur_i][0] ); # go out;

        my $comp;
        if( defined $ref_to_sorted_Fhkl->[$cur_i][0] &&
            defined $ref_to_sorted_Fhkl->[$cur_i + $add][0] ) {
            $comp =
                ( (sprintf '%.4f', $ref_to_sorted_Fhkl->[$cur_i][0]) ==
                  (sprintf '%.4f', $ref_to_sorted_Fhkl->[$cur_i + $add][0]) );
        } elsif ( !defined $ref_to_sorted_Fhkl->[$cur_i + $add][0] ) {
            $comp = 0; # last element not exist;
        } else {
            $comp = 0; # elements are not eqaul;
        }
        ## print "Condition 1: $a == $b \n";
        # ... then merging of Fhkl values;
        if( !defined $output[$adding_i] && $comp ) {
            # Friedel's twins are detected, creating;
            $output[$adding_i] =
                [ @{$ref_to_sorted_Fhkl->[$cur_i]},
                  $ref_to_sorted_Fhkl->[$cur_i + $add][1] ];
            $add++; # seaching ..;
        } elsif ( !defined $output[$adding_i] && !$comp ) {
            # if not equal and last element don't exist; creating and
            # next;
            $output[$adding_i] =
                $ref_to_sorted_Fhkl->[$cur_i]; # the Friedel's
            # twins not detected;
            $adding_i++; # next element ;
            $cur_i += 1; # go to next element in sorted array
            $add = 1; # restart;
            next;
        } elsif ($add > 1 && $comp) {
            # more same Friedel's twins are detected, adding;
            push @{$output[$adding_i]},
                 $ref_to_sorted_Fhkl->[$cur_i + $add][1];
            $add++; # searching more;
        } else {
            $adding_i++;
            $cur_i += $add;
            $add = 1;
            next;
        }
    } # end while 
    return \@output;
}

#------------------------------------------------------------------------------
# Function for extracting of data details about
# covalent bonds
#
# Parameters:
#    values - a reference to array of hashes where a data from the CIF
#    file is stored
#
# Returns:
# \@ = (
#           # [0]
#           {
#               neighbors => [H134,H135],
#           },
#           # [1]
#           {
#               neighbors => [C13,C15],
#           },
#
sub get_neighbors
{
    my( $values ) = @_;

    my $bond_atoms_1 = $values->{'_geom_bond_atom_site_label_1'};
    my $bond_atoms_2 = $values->{'_geom_bond_atom_site_label_2'};

    my @neighbors;
    foreach my $label ( @{ $values->{'_atom_site_label'} } ) {

        my %atom_info;
        $atom_info{'neighbors'} = [];
        for my $i (0..$#{$bond_atoms_1}) {

            my $atom1_label = $bond_atoms_1->[$i];
            my $atom2_label = $bond_atoms_2->[$i];

            if( $label eq $atom1_label ) {
                push @{$atom_info{'neighbors'}}, $atom2_label;
            } elsif( $label eq $atom2_label ) {
                push @{$atom_info{'neighbors'}}, $atom1_label;
            }
        }
        push @neighbors, \%atom_info;
    }

    return \@neighbors;
}
