/*----------------------------------------------------------------------
  SerialReax - Reax Force Field Simulator

  Copyright (2010) Purdue University
  Hasan Metin Aktulga, haktulga@cs.purdue.edu
  Joseph Fogarty, jcfogart@mail.usf.edu
  Sagar Pandit, pandit@usf.edu
  Ananth Y Grama, ayg@cs.purdue.edu

  This program is free software; you can redistribute it and/or
  modify it under the terms of the GNU General Public License as
  published by the Free Software Foundation; either version 2 of
  the License, or (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  See the GNU General Public License for more details:
  <http://www.gnu.org/licenses/>.
  ----------------------------------------------------------------------*/

#include "forces.h"

#include "box.h"
#include "bond_orders.h"
#include "bonds.h"
#include "charges.h"
#include "hydrogen_bonds.h"
#if defined(TEST_FORCES)
  #include "io_tools.h"
#endif
#include "list.h"
#include "multi_body.h"
#include "nonbonded.h"
#include "system_props.h"
#include "torsion_angles.h"
#include "tool_box.h"
#include "valence_angles.h"
#include "vector.h"


typedef enum
{
    DIAGONAL = 0,
    OFF_DIAGONAL = 1,
} MATRIX_ENTRY_POSITION;


#if defined(TEST_FORCES)
static int compare_bonds( const void *p1, const void *p2 )
{
    return ((bond_data *)p1)->nbr - ((bond_data *)p2)->nbr;
}
#endif


void Init_Bonded_Force_Functions( control_params *control )
{
    control->intr_funcs[0] = &BO;
    control->intr_funcs[1] = &Bonds;
    control->intr_funcs[2] = &Atom_Energy;
    control->intr_funcs[3] = &Valence_Angles;
    control->intr_funcs[4] = &Torsion_Angles;
    if ( control->hbond_cut > 0.0 )
    {
        control->intr_funcs[5] = &Hydrogen_Bonds;
    }
    else
    {
        control->intr_funcs[5] = NULL;
    }
    control->intr_funcs[6] = NULL;
    control->intr_funcs[7] = NULL;
    control->intr_funcs[8] = NULL;
    control->intr_funcs[9] = NULL;
}


static void Compute_Bonded_Forces( reax_system *system, control_params *control,
        simulation_data *data, static_storage *workspace, reax_list **lists,
        output_controls *out_control )
{
    int i;

#if defined(TEST_ENERGY)
    /* Mark beginning of a new timestep in each energy file */
    fprintf( out_control->ebond, "step: %d\n%6s%6s%12s%12s%12s\n",
             data->step, "atom1", "atom2", "bo", "ebond", "total" );
    fprintf( out_control->elp, "step: %d\n%6s%12s%12s%12s\n",
             data->step, "atom", "nlp", "elp", "total" );
    fprintf( out_control->eov, "step: %d\n%6s%12s%12s\n",
             data->step, "atom", "eov", "total" );
    fprintf( out_control->eun, "step: %d\n%6s%12s%12s\n",
             data->step, "atom", "eun", "total" );
    fprintf( out_control->eval, "step: %d\n%6s%6s%6s%12s%12s%12s%12s%12s%12s\n",
             data->step, "atom1", "atom2", "atom3",
             "angle", "bo(12)", "bo(23)", "eval", "epen", "total" );
    fprintf( out_control->epen, "step: %d\n%6s%6s%6s%12s%12s%12s%12s%12s\n",
             data->step, "atom1", "atom2", "atom3",
             "angle", "bo(12)", "bo(23)", "epen", "total" );
    fprintf( out_control->ecoa, "step: %d\n%6s%6s%6s%12s%12s%12s%12s%12s\n",
             data->step, "atom1", "atom2", "atom3",
             "angle", "bo(12)", "bo(23)", "ecoa", "total" );
    fprintf( out_control->ehb,  "step: %d\n%6s%6s%6s%12s%12s%12s%12s%12s\n",
             data->step, "atom1", "atom2", "atom3",
             "r(23)", "angle", "bo(12)", "ehb", "total" );
    fprintf( out_control->etor, "step: %d\n%6s%6s%6s%6s%12s%12s%12s%12s\n",
             data->step, "atom1", "atom2", "atom3", "atom4",
             "phi", "bo(23)", "etor", "total" );
    fprintf( out_control->econ, "step:%d\n%6s%6s%6s%6s%12s%12s%12s%12s%12s%12s\n",
             data->step, "atom1", "atom2", "atom3", "atom4",
             "phi", "bo(12)", "bo(23)", "bo(34)", "econ", "total" );
#endif

    /* function calls for bonded interactions */
    for ( i = 0; i < NUM_INTRS; i++ )
    {
        if ( control->intr_funcs[i] != NULL )
        {
            (control->intr_funcs[i])( system, control, data, workspace,
                    lists, out_control );
        }
    }

#if defined(TEST_FORCES)
    /* function calls for printing bonded interactions */
    for ( i = 0; i < NUM_INTRS; i++ )
    {
        if ( control->print_intr_funcs[i] != NULL )
        {
            (control->print_intr_funcs[i])( system, control, data, workspace,
                    lists, out_control );
        }
    }
#endif
}


static void Compute_NonBonded_Forces( reax_system *system, control_params *control,
        simulation_data *data, static_storage *workspace,
        reax_list** lists, output_controls *out_control, int realloc )
{
    real t_start, t_elapsed;

#if defined(TEST_ENERGY)
    fprintf( out_control->evdw, "step: %d\n%6s%6s%12s%12s%12s\n",
             data->step, "atom1", "atom2", "r12", "evdw", "total" );
    fprintf( out_control->ecou, "step: %d\n%6s%6s%12s%12s%12s%12s%12s\n",
             data->step, "atom1", "atom2", "r12", "q1", "q2", "ecou", "total" );
#endif

    t_start = Get_Time( );
    Compute_Charges( system, control, data, workspace, out_control, realloc );
    t_elapsed = Get_Timing_Info( t_start );
    data->timing.cm += t_elapsed;
    
    if ( control->cm_solver_pre_comp_refactor == -1 )
    {
        if ( data->step <= 4 || is_refactoring_step( control, data ) )
        {
            if ( is_refactoring_step( control, data ) )
            {
                data->timing.cm_last_pre_comp = data->timing.cm_solver_pre_comp;
            }

            data->timing.cm_optimum = data->timing.cm_solver_pre_app
                + data->timing.cm_solver_spmv
                + data->timing.cm_solver_vector_ops
                + data->timing.cm_solver_orthog
                + data->timing.cm_solver_tri_solve;
            data->timing.cm_total_loss = 0.0;
        }
        else
        {
            data->timing.cm_total_loss += data->timing.cm_solver_pre_app
                + data->timing.cm_solver_spmv
                + data->timing.cm_solver_vector_ops
                + data->timing.cm_solver_orthog
                + data->timing.cm_solver_tri_solve
                - data->timing.cm_optimum;
        }
    }

    if ( control->tabulate <= 0 )
    {
        vdW_Coulomb_Energy( system, control, data, workspace, lists, out_control );
    }
    else
    {
        Tabulated_vdW_Coulomb_Energy( system, control, data, workspace,
                lists, out_control );
    }

#if defined(TEST_FORCES)
    Print_vdW_Coulomb_Forces( system, control, data, workspace,
            lists, out_control );
#endif
}


/* This version of Compute_Total_Force computes forces from coefficients
   accumulated by all interaction functions. Saves enormous time & space! */
static void Compute_Total_Force( reax_system *system, control_params *control,
        simulation_data *data, static_storage *workspace, reax_list **lists )
{
    int i;
    reax_list *bonds;

    bonds = lists[BONDS];

#if defined(_OPENMP)
    #pragma omp parallel default(shared)
#endif
    {
        int pj;
#if defined(_OPENMP)
        int j;
#endif

        if ( control->compute_pressure == FALSE
                && (control->ensemble == NVE || control->ensemble == nhNVT
                    || control->ensemble == bNVT) )
        {
#if defined(_OPENMP)
            #pragma omp for schedule(static)
#endif
            for ( i = 0; i < system->N; ++i )
            {
                for ( pj = Start_Index(i, bonds); pj < End_Index(i, bonds); ++pj )
                {
                    if ( i <= bonds->bond_list[pj].nbr )
                    {
                        Add_dBond_to_Forces( i, pj, system, data, workspace, lists );
                    }
                }
            }
        }
        else if ( control->ensemble == sNPT || control->ensemble == iNPT
                || control->ensemble == aNPT || control->compute_pressure == TRUE )
        {
#if defined(_OPENMP)
            #pragma omp for schedule(static)
#endif
            for ( i = 0; i < system->N; ++i )
            {
                for ( pj = Start_Index(i, bonds); pj < End_Index(i, bonds); ++pj )
                {
                    if ( i <= bonds->bond_list[pj].nbr )
                    {
                        Add_dBond_to_Forces_NPT( i, pj, system, data, workspace, lists );
                    }
                }
            }
        }

#if defined(_OPENMP)
        /* reduction (sum) on thread-local force vectors */
        #pragma omp for schedule(static)
        for ( i = 0; i < system->N; ++i )
        {
            for ( j = 0; j < control->num_threads; ++j )
            {
                rvec_Add( system->atoms[i].f, workspace->f_local[j * system->N + i] );
            }
        }
#endif
    }
}


static void Validate_Lists( static_storage *workspace, reax_list **lists, int step, int n,
        int Hmax, int Htop, int num_bonds, int num_hbonds )
{
    int i, flag;
    reax_list *bonds, *hbonds;

    bonds = lists[BONDS];
    hbonds = lists[HBONDS];

    /* far neighbors */
    if ( Htop > Hmax * DANGER_ZONE )
    {
        workspace->realloc.Htop = Htop;
        if ( Htop > Hmax )
        {
            fprintf( stderr,
                     "[ERROR] step%d - ran out of space on H matrix: Htop=%d, max = %d",
                     step, Htop, Hmax );
            exit( INSUFFICIENT_MEMORY );
        }
    }

    /* bond list */
    flag = -1;
    workspace->realloc.num_bonds = num_bonds;
    for ( i = 0; i < n - 1; ++i )
    {
        if ( End_Index(i, bonds) >= Start_Index(i + 1, bonds) - 2 )
        {
            workspace->realloc.bonds = 1;
            if ( End_Index(i, bonds) > Start_Index(i + 1, bonds) )
            {
                flag = i;
            }
        }
    }

    if ( flag > -1 )
    {
        fprintf( stderr, "[ERROR] step%d-bondchk failed: i=%d end(i)=%d str(i+1)=%d\n",
                 step, flag, End_Index(flag, bonds), Start_Index(flag + 1, bonds) );
        exit( INSUFFICIENT_MEMORY );
    }

    if ( End_Index(i, bonds) >= bonds->total_intrs - 2 )
    {
        workspace->realloc.bonds = 1;

        if ( End_Index(i, bonds) > bonds->total_intrs )
        {
            fprintf( stderr, "[ERROR] step%d-bondchk failed: i=%d end(i)=%d bond_end=%d\n",
                     step, flag, End_Index(i, bonds), bonds->total_intrs );
            exit( INSUFFICIENT_MEMORY );
        }
    }


    /* hbonds list */
    if ( workspace->num_H > 0 )
    {
        flag = -1;
        workspace->realloc.num_hbonds = num_hbonds;
        for ( i = 0; i < workspace->num_H - 1; ++i )
        {
            if ( Num_Entries(i, hbonds) >=
                    (Start_Index(i + 1, hbonds) - Start_Index(i, hbonds)) * DANGER_ZONE )
            {
                workspace->realloc.hbonds = 1;
                if ( End_Index(i, hbonds) > Start_Index(i + 1, hbonds) )
                {
                    flag = i;
                }
            }
        }

        if ( flag > -1 )
        {
            fprintf( stderr, "[ERROR] step%d-hbondchk failed: i=%d end(i)=%d str(i+1)=%d\n",
                     step, flag, End_Index(flag, hbonds), Start_Index(flag + 1, hbonds) );
            exit( INSUFFICIENT_MEMORY );
        }

        if ( Num_Entries(i, hbonds) >=
                (hbonds->total_intrs - Start_Index(i, hbonds)) * DANGER_ZONE )
        {
            workspace->realloc.hbonds = 1;

            if ( End_Index(i, hbonds) > hbonds->total_intrs )
            {
                fprintf( stderr, "[ERROR] step%d-hbondchk failed: i=%d end(i)=%d hbondend=%d\n",
                         step, flag, End_Index(i, hbonds), hbonds->total_intrs );
                exit( INSUFFICIENT_MEMORY );
            }
        }
    }
}


static inline real Init_Charge_Matrix_Entry_Tab( reax_system *system,
        control_params *control, LR_lookup_table **LR, int i, int j,
        real r_ij, MATRIX_ENTRY_POSITION pos )
{
    int r;
    real base, dif, val, ret;
    LR_lookup_table *t;

    ret = 0.0;

    switch ( control->charge_method )
    {
    case QEQ_CM:
    //TODO: tabulate other portions of matrices for EE, ACKS2?
    case EE_CM:
    case ACKS2_CM:
        switch ( pos )
        {
            case OFF_DIAGONAL:
                t = &LR[MIN( system->atoms[i].type, system->atoms[j].type )]
                       [MAX( system->atoms[i].type, system->atoms[j].type )];

                /* cubic spline interpolation */
                r = (int)(r_ij * t->inv_dx);
                if ( r == 0 ) 
                {
                    ++r;
                }
                base = (real)(r + 1) * t->dx;
                dif = r_ij - base;
                val = ((t->ele[r].d * dif + t->ele[r].c) * dif + t->ele[r].b)
                    * dif + t->ele[r].a;
                val *= EV_to_KCALpMOL / C_ELE;

                ret = ((i == j) ? 0.5 : 1.0) * val;
            break;

            case DIAGONAL:
                ret = system->reax_param.sbp[system->atoms[i].type].eta;
            break;

            default:
                fprintf( stderr, "[ERROR] Invalid matrix position. Terminating...\n" );
                exit( INVALID_INPUT );
            break;
        }
        break;

    default:
        fprintf( stderr, "[ERROR] Invalid charge method. Terminating...\n" );
        exit( INVALID_INPUT );
        break;
    }

    return ret;
}


static inline real Init_Charge_Matrix_Entry( reax_system *system,
        control_params *control, static_storage *workspace,
        int i, int j, real r_ij, MATRIX_ENTRY_POSITION pos )
{
    real Tap, dr3gamij_1, dr3gamij_3, ret;

    ret = 0.0;

    switch ( control->charge_method )
    {
    case QEQ_CM:
    case EE_CM:
    case ACKS2_CM:
        switch ( pos )
        {
            case OFF_DIAGONAL:
                Tap = workspace->Tap[7] * r_ij + workspace->Tap[6];
                Tap = Tap * r_ij + workspace->Tap[5];
                Tap = Tap * r_ij + workspace->Tap[4];
                Tap = Tap * r_ij + workspace->Tap[3];
                Tap = Tap * r_ij + workspace->Tap[2];
                Tap = Tap * r_ij + workspace->Tap[1];
                Tap = Tap * r_ij + workspace->Tap[0];

                /* shielding */
                dr3gamij_1 = r_ij * r_ij * r_ij
                        + POW( system->reax_param.tbp[system->atoms[i].type][system->atoms[j].type].gamma, -3.0 );
                dr3gamij_3 = POW( dr3gamij_1 , 1.0 / 3.0 );

                /* i == j: periodic self-interaction term
                 * i != j: general interaction term */
                ret = ((i == j) ? 0.5 : 1.0) * Tap * EV_to_KCALpMOL / dr3gamij_3;
            break;

            case DIAGONAL:
                ret = system->reax_param.sbp[system->atoms[i].type].eta;
            break;

            default:
                fprintf( stderr, "[ERROR] Invalid matrix position. Terminating...\n" );
                exit( INVALID_INPUT );
            break;
        }
        break;

    default:
        fprintf( stderr, "[ERROR] Invalid charge method. Terminating...\n" );
        exit( INVALID_INPUT );
        break;
    }

    return ret;
}


static void Init_Charge_Matrix_Remaining_Entries( reax_system *system,
        control_params *control, reax_list *far_nbr_list,
        sparse_matrix * H, sparse_matrix * H_sp,
        int * Htop, int * H_sp_top )
{
    int i, j, pj, target, val_flag;
    real d, xcut, bond_softness, * X_diag;

    switch ( control->charge_method )
    {
        case QEQ_CM:
            break;

        case EE_CM:
            if ( system->num_molec_charge_constraints == 0 )
            {
                H->start[system->N_cm - 1] = *Htop;
                H_sp->start[system->N_cm - 1] = *H_sp_top;

                for ( i = 0; i < system->N_cm - 1; ++i )
                {
                    /* total charge constraint on QM atoms */
#if defined(QMMM)
                    if ( system->atoms[i].qmmm_mask == TRUE )
                    {
#endif
                    H->j[*Htop] = i;
                    H->val[*Htop] = 1.0;

                    H_sp->j[*H_sp_top] = i;
                    H_sp->val[*H_sp_top] = 1.0;
#if defined(QMMM)
                    }
#endif

                    *Htop = *Htop + 1;
                    *H_sp_top = *H_sp_top + 1;
                }

                H->j[*Htop] = system->N_cm - 1;
                H->val[*Htop] = 0.0;
                *Htop = *Htop + 1;

                H_sp->j[*H_sp_top] = system->N_cm - 1;
                H_sp->val[*H_sp_top] = 0.0;
                *H_sp_top = *H_sp_top + 1;
            }
            else
            {
                for ( i = 0; i < system->num_molec_charge_constraints; ++i )
                {
                    H->start[system->N + i] = *Htop;
                    H_sp->start[system->N + i] = *H_sp_top;

                    for ( j = system->molec_charge_constraint_ranges[2 * i];
                            j <= system->molec_charge_constraint_ranges[2 * i + 1]; ++j )
                    {
                        /* molecule charge constraint on QM atoms */
#if defined(QMMM)
                        if ( system->atoms[j - 1].qmmm_mask == TRUE )
                        {
#endif
                        H->j[*Htop] = j - 1;
                        H->val[*Htop] = 1.0;

                        H_sp->j[*H_sp_top] = j - 1;
                        H_sp->val[*H_sp_top] = 1.0;

                        *Htop = *Htop + 1;
                        *H_sp_top = *H_sp_top + 1;
#if defined(QMMM)
                        }
#endif
                    }

                    /* explicit zeros on diagonals */
                    H->j[*Htop] = system->N + i;
                    H->val[*Htop] = 0.0; 
                    *Htop = *Htop + 1;

                    H_sp->j[*H_sp_top] = system->N + i;
                    H_sp->val[*H_sp_top] = 0.0;
                    *H_sp_top = *H_sp_top + 1;
                }
            }
            break;

        case ACKS2_CM:
            X_diag = smalloc( sizeof(real) * system->N, __FILE__, __LINE__ );

            for ( i = 0; i < system->N; ++i )
            {
                X_diag[i] = 0.0;
            }

            for ( i = 0; i < system->N; ++i )
            {
                H->start[system->N + i] = *Htop;
                H_sp->start[system->N + i] = *H_sp_top;

                /* constraint on ref. value for kinetic energy potential */
                H->j[*Htop] = i;
                H->val[*Htop] = 1.0;
                *Htop = *Htop + 1;

                H_sp->j[*H_sp_top] = i;
                H_sp->val[*H_sp_top] = 1.0;
                *H_sp_top = *H_sp_top + 1;

                /* kinetic energy terms */
                for ( pj = Start_Index(i, far_nbr_list); pj < End_Index(i, far_nbr_list); ++pj )
                {
                    /* exclude self-periodic images of atoms for
                     * kinetic energy term because the effective
                     * potential is the same on an atom and its periodic image */
                    if ( far_nbr_list->far_nbr_list[pj].d <= control->nonb_cut )
                    {
                        j = far_nbr_list->far_nbr_list[pj].nbr;

                        xcut = 0.5 * ( system->reax_param.sbp[ system->atoms[i].type ].b_s_acks2
                                + system->reax_param.sbp[ system->atoms[j].type ].b_s_acks2 );

                        if ( far_nbr_list->far_nbr_list[pj].d < xcut )
                        {
                            d = far_nbr_list->far_nbr_list[pj].d / xcut;
                            bond_softness = system->reax_param.gp.l[34] * POW( d, 3.0 )
                                * POW( 1.0 - d, 6.0 );

                            if ( bond_softness > 0.0 )
                            {
                                val_flag = FALSE;

                                for ( target = H->start[system->N + i]; target < *Htop; ++target )
                                {
                                    if ( H->j[target] == system->N + j )
                                    {
                                        H->val[target] += bond_softness;
                                        val_flag = TRUE;
                                        break;
                                    }
                                }

                                if ( val_flag == FALSE )
                                {
                                    H->j[*Htop] = system->N + j;
                                    H->val[*Htop] = bond_softness;
                                    ++(*Htop);
                                }

                                val_flag = FALSE;

                                for ( target = H_sp->start[system->N + i]; target < *H_sp_top; ++target )
                                {
                                    if ( H_sp->j[target] == system->N + j )
                                    {
                                        H_sp->val[target] += bond_softness;
                                        val_flag = TRUE;
                                        break;
                                    }
                                }

                                if ( val_flag == FALSE )
                                {
                                    H_sp->j[*H_sp_top] = system->N + j;
                                    H_sp->val[*H_sp_top] = bond_softness;
                                    ++(*H_sp_top);
                                }

                                X_diag[i] -= bond_softness;
                                X_diag[j] -= bond_softness;
                            }
                        }
                    }
                }

                /* placeholders for diagonal entries, to be replaced below */
                H->j[*Htop] = system->N + i;
                H->val[*Htop] = 0.0;
                *Htop = *Htop + 1;

                H_sp->j[*H_sp_top] = system->N + i;
                H_sp->val[*H_sp_top] = 0.0;
                *H_sp_top = *H_sp_top + 1;
            }

            /* second to last row */
            H->start[system->N_cm - 2] = *Htop;
            H_sp->start[system->N_cm - 2] = *H_sp_top;

            /* place accumulated diagonal entries (needed second to last row marker above before this code) */
            for ( i = system->N; i < 2 * system->N; ++i )
            {
                for ( pj = H->start[i]; pj < H->start[i + 1]; ++pj )
                {
                    if ( H->j[pj] == i )
                    {
                        H->val[pj] = X_diag[i - system->N];
                        break;
                    }
                }

                for ( pj = H_sp->start[i]; pj < H_sp->start[i + 1]; ++pj )
                {
                    if ( H_sp->j[pj] == i )
                    {
                        H_sp->val[pj] = X_diag[i - system->N];
                        break;
                    }
                }
            }

            /* coupling with the kinetic energy potential */
            for ( i = 0; i < system->N; ++i )
            {
                H->j[*Htop] = system->N + i;
                H->val[*Htop] = 1.0;
                *Htop = *Htop + 1;

                H_sp->j[*H_sp_top] = system->N + i;
                H_sp->val[*H_sp_top] = 1.0;
                *H_sp_top = *H_sp_top + 1;
            }

            /* explicitly store zero on diagonal */
            H->j[*Htop] = system->N_cm - 2;
            H->val[*Htop] = 0.0;
            *Htop = *Htop + 1;

            H_sp->j[*H_sp_top] = system->N_cm - 2;
            H_sp->val[*H_sp_top] = 0.0;
            *H_sp_top = *H_sp_top + 1;

            /* last row */
            H->start[system->N_cm - 1] = *Htop;
            H_sp->start[system->N_cm - 1] = *H_sp_top;

            for ( i = 0; i < system->N; ++i )
            {
                H->j[*Htop] = i;
                H->val[*Htop] = 1.0;
                *Htop = *Htop + 1;

                H_sp->j[*H_sp_top] = i;
                H_sp->val[*H_sp_top] = 1.0;
                *H_sp_top = *H_sp_top + 1;
            }

            /* explicitly store zero on diagonal */
            H->j[*Htop] = system->N_cm - 1;
            H->val[*Htop] = 0.0;
            *Htop = *Htop + 1;

            H_sp->j[*H_sp_top] = system->N_cm - 1;
            H_sp->val[*H_sp_top] = 0.0;
            *H_sp_top = *H_sp_top + 1;

            sfree( X_diag, __FILE__, __LINE__ );
            break;

        default:
            break;
    }
}


/* Generate bond list (full format), hydrogen bond list (full format),
 * and charge matrix (half symmetric format)
 * from the far neighbors list (with distance updates, if necessary)  */
static void Init_Forces( reax_system *system, control_params *control,
        simulation_data *data, static_storage *workspace,
        reax_list **lists, output_controls *out_control )
{
    int i, j, pj, target;
    int start_i, end_i;
    int type_i, type_j;
    int Htop, H_sp_top, btop_i, btop_j, num_bonds, num_hbonds;
    int ihb, jhb, ihb_top, jhb_top;
    int flag, flag_sp, val_flag, renbr;
    real r_ij, r2, val;
    real C12, C34, C56;
    real Cln_BOp_s, Cln_BOp_pi, Cln_BOp_pi2;
    real BO, BO_s, BO_pi, BO_pi2;
    sparse_matrix *H, *H_sp;
    reax_list *far_nbrs, *bonds, *hbonds;
    single_body_parameters *sbp_i, *sbp_j;
    two_body_parameters *twbp;
    far_neighbor_data *nbr_pj;
    reax_atom *atom_i, *atom_j;
    bond_data *ibond, *jbond;
    bond_order_data *bo_ij, *bo_ji;

    far_nbrs = lists[FAR_NBRS];
    bonds = lists[BONDS];
    hbonds = lists[HBONDS];
    H = &workspace->H;
    H_sp = &workspace->H_sp;
    Htop = 0;
    H_sp_top = 0;
    num_bonds = 0;
    num_hbonds = 0;
    btop_i = 0;
    btop_j = 0;
    renbr = ((data->step - data->prev_steps) % control->reneighbor) == 0 ? TRUE : FALSE;

    for ( i = 0; i < system->N; ++i )
    {
        atom_i = &system->atoms[i];
        type_i = atom_i->type;
        start_i = Start_Index( i, far_nbrs );
        end_i = End_Index( i, far_nbrs );
        H->start[i] = Htop;
        H_sp->start[i] = H_sp_top;
        btop_i = End_Index( i, bonds );
        sbp_i = &system->reax_param.sbp[type_i];

        if ( control->hbond_cut > 0.0 )
        {
            ihb = sbp_i->p_hbond;

            if ( ihb == H_ATOM )
            {
                ihb_top = End_Index( workspace->hbond_index[i], hbonds );
            }
            else
            {
                ihb_top = -1;
            }
        }
        else
        {
            ihb = NON_H_BONDING_ATOM;
            ihb_top = -1;
        }

        for ( pj = start_i; pj < end_i; ++pj )
        {
            nbr_pj = &far_nbrs->far_nbr_list[pj];
            j = nbr_pj->nbr;
            flag = FALSE;
            flag_sp = FALSE;

#if defined(QMMM)
            if ( system->atoms[i].qmmm_mask == TRUE
                    || system->atoms[j].qmmm_mask == TRUE )
            {
#endif	
            /* check if reneighboring step --
             * atomic distances just computed via
             * Verlet list, so use current distances */
            if ( renbr == TRUE )
            {
                if ( nbr_pj->d <= control->nonb_cut )
                {
                    flag = TRUE;

                    if ( nbr_pj->d <= control->nonb_sp_cut )
                    {
                        flag_sp = TRUE;
                    }
                }
            }
            /* update atomic distances */
            else
            {
                atom_j = &system->atoms[j];
                nbr_pj->d = control->compute_atom_distance( &system->box,
                        atom_i->x, atom_j->x, atom_i->rel_map,
                        atom_j->rel_map, nbr_pj->rel_box,
                        nbr_pj->dvec );

                if ( nbr_pj->d <= control->nonb_cut )
                {
                    flag = TRUE;

                    if ( nbr_pj->d <= control->nonb_sp_cut )
                    {
                        flag_sp = TRUE;
                    }
                }
            }

            if ( flag == TRUE )
            {
                type_j = system->atoms[j].type;
                sbp_j = &system->reax_param.sbp[type_j];
                twbp = &system->reax_param.tbp[type_i][type_j];
                r_ij = nbr_pj->d;

                val = Init_Charge_Matrix_Entry( system, control,
                            workspace, i, j, r_ij, OFF_DIAGONAL );
                val_flag = FALSE;

                for ( target = H->start[i]; target < Htop; ++target )
                {
                    if ( H->j[target] == j )
                    {
                        H->val[target] += val;
                        val_flag = TRUE;
                        break;
                    }
                }

                if ( val_flag == FALSE )
                {
                    H->j[Htop] = j;
                    H->val[Htop] = val;
                    ++Htop;
                }

                /* H_sp matrix entry */
                if ( flag_sp == TRUE )
                {
                    val_flag = FALSE;

                    for ( target = H_sp->start[i]; target < H_sp_top; ++target )
                    {
                        if ( H_sp->j[target] == j )
                        {
                            H_sp->val[target] += val;
                            val_flag = TRUE;
                            break;
                        }
                    }

                    if ( val_flag == FALSE )
                    {
                        H_sp->j[H_sp_top] = j;
                        H_sp->val[H_sp_top] = val;
                        ++H_sp_top;
                    }
                }
#if defined(QMMM)
                if ( system->atoms[i].qmmm_mask == TRUE
                        && system->atoms[j].qmmm_mask == TRUE )
                {
#endif
                /* Only non-dummy atoms can form bonds */
                if ( system->atoms[i].is_dummy == FALSE
                        && system->atoms[j].is_dummy == FALSE )
                {

                    /* hydrogen bond lists */
                    if ( control->hbond_cut > 0.0
                            && (ihb == H_ATOM || ihb == H_BONDING_ATOM)
                            && nbr_pj->d <= control->hbond_cut )
                    {
                        jhb = sbp_j->p_hbond;

                        if ( ihb == H_ATOM && jhb == H_BONDING_ATOM )
                        {
                            hbonds->hbond_list[ihb_top].nbr = j;
                            hbonds->hbond_list[ihb_top].scl = 1;
                            hbonds->hbond_list[ihb_top].ptr = nbr_pj;
                            ++ihb_top;
                            ++num_hbonds;
                        }
                        else if ( ihb == H_BONDING_ATOM && jhb == H_ATOM )
                        {
                            jhb_top = End_Index( workspace->hbond_index[j], hbonds );
                            hbonds->hbond_list[jhb_top].nbr = i;
                            hbonds->hbond_list[jhb_top].scl = -1;
                            hbonds->hbond_list[jhb_top].ptr = nbr_pj;
                            Set_End_Index( workspace->hbond_index[j], jhb_top + 1, hbonds );
                            ++num_hbonds;
                        }
                    }

                    /* uncorrected bond orders */
                    if ( nbr_pj->d <= control->bond_cut )
                    {
                        r2 = SQR( r_ij );

                        if ( sbp_i->r_s > 0.0 && sbp_j->r_s > 0.0 )
                        {
                            C12 = twbp->p_bo1 * POW( r_ij / twbp->r_s, twbp->p_bo2 );
                            BO_s = (1.0 + control->bo_cut) * EXP( C12 );
                        }
                        else
                        {
                            C12 = 0.0;
                            BO_s = 0.0;
                        }

                        if ( sbp_i->r_pi > 0.0 && sbp_j->r_pi > 0.0 )
                        {
                            C34 = twbp->p_bo3 * POW( r_ij / twbp->r_p, twbp->p_bo4 );
                            BO_pi = EXP( C34 );
                        }
                        else
                        {
                            C34 = 0.0;
                            BO_pi = 0.0;
                        }

                        if ( sbp_i->r_pi_pi > 0.0 && sbp_j->r_pi_pi > 0.0 )
                        {
                            C56 = twbp->p_bo5 * POW( r_ij / twbp->r_pp, twbp->p_bo6 );
                            BO_pi2 = EXP( C56 );
                        }
                        else
                        {
                            C56 = 0.0;
                            BO_pi2 = 0.0;
                        }

                        /* Initially BO values are the uncorrected ones, page 1 */
                        BO = BO_s + BO_pi + BO_pi2;

                        if ( BO >= control->bo_cut )
                        {
                            num_bonds += 2;
                            /****** bonds i-j and j-i ******/
                            ibond = &bonds->bond_list[btop_i];
                            btop_j = End_Index( j, bonds );
                            jbond = &bonds->bond_list[btop_j];

                            ibond->nbr = j;
                            jbond->nbr = i;
                            ibond->d = r_ij;
                            jbond->d = r_ij;
                            rvec_Copy( ibond->dvec, nbr_pj->dvec );
                            rvec_Scale( jbond->dvec, -1, nbr_pj->dvec );
                            ivec_Copy( ibond->rel_box, nbr_pj->rel_box );
                            ivec_Scale( jbond->rel_box, -1, nbr_pj->rel_box );
                            ibond->dbond_index = btop_i;
                            jbond->dbond_index = btop_i;
                            ibond->sym_index = btop_j;
                            jbond->sym_index = btop_i;
                            ++btop_i;
                            Set_End_Index( j, btop_j + 1, bonds );

                            bo_ij = &ibond->bo_data;
                            bo_ij->BO = BO;
                            bo_ij->BO_s = BO_s;
                            bo_ij->BO_pi = BO_pi;
                            bo_ij->BO_pi2 = BO_pi2;
                            bo_ji = &jbond->bo_data;
                            bo_ji->BO = BO;
                            bo_ji->BO_s = BO_s;
                            bo_ji->BO_pi = BO_pi;
                            bo_ji->BO_pi2 = BO_pi2;

                            /* Bond Order page2-3, derivative of total bond order prime */
                            Cln_BOp_s = twbp->p_bo2 * C12 / r2;
                            Cln_BOp_pi = twbp->p_bo4 * C34 / r2;
                            Cln_BOp_pi2 = twbp->p_bo6 * C56 / r2;

                            /* Only dln_BOp_xx wrt. dr_i is stored here, note that
                               dln_BOp_xx/dr_i = -dln_BOp_xx/dr_j and all others are 0 */
                            rvec_Scale( bo_ij->dln_BOp_s, -bo_ij->BO_s * Cln_BOp_s, ibond->dvec );
                            rvec_Scale( bo_ij->dln_BOp_pi, -bo_ij->BO_pi * Cln_BOp_pi, ibond->dvec );
                            rvec_Scale( bo_ij->dln_BOp_pi2,
                                    -bo_ij->BO_pi2 * Cln_BOp_pi2, ibond->dvec );
                            rvec_Scale( bo_ji->dln_BOp_s, -1., bo_ij->dln_BOp_s );
                            rvec_Scale( bo_ji->dln_BOp_pi, -1., bo_ij->dln_BOp_pi );
                            rvec_Scale( bo_ji->dln_BOp_pi2, -1., bo_ij->dln_BOp_pi2 );

                            /* Only dBOp wrt. dr_i is stored here, note that
                               dBOp/dr_i = -dBOp/dr_j and all others are 0 */
                            rvec_Scale( bo_ij->dBOp, -(bo_ij->BO_s * Cln_BOp_s
                                        + bo_ij->BO_pi * Cln_BOp_pi
                                        + bo_ij->BO_pi2 * Cln_BOp_pi2), ibond->dvec );
                            rvec_Scale( bo_ji->dBOp, -1., bo_ij->dBOp );

                            rvec_Add( workspace->dDeltap_self[i], bo_ij->dBOp );
                            rvec_Add( workspace->dDeltap_self[j], bo_ji->dBOp );

                            bo_ij->BO_s -= control->bo_cut;
                            bo_ij->BO -= control->bo_cut;
                            bo_ji->BO_s -= control->bo_cut;
                            bo_ji->BO -= control->bo_cut;
                            workspace->total_bond_order[i] += bo_ij->BO;
                            workspace->total_bond_order[j] += bo_ji->BO;
                            bo_ij->Cdbo = 0.0;
                            bo_ij->Cdbopi = 0.0;
                            bo_ij->Cdbopi2 = 0.0;
                            bo_ji->Cdbo = 0.0;
                            bo_ji->Cdbopi = 0.0;
                            bo_ji->Cdbopi2 = 0.0;

                            Set_End_Index( j, btop_j + 1, bonds );
                        }
                    }
                }
#if defined(QMMM)
                }
#endif
            }
#if defined(QMMM)
            }
#endif
        }

        /* diagonal entry */
        H->j[Htop] = i;
        H->val[Htop] = Init_Charge_Matrix_Entry( system, control,
                workspace, i, i, r_ij, DIAGONAL );
        ++Htop;

        H_sp->j[H_sp_top] = i;
        H_sp->val[H_sp_top] = H->val[Htop - 1];
        ++H_sp_top;

        Set_End_Index( i, btop_i, bonds );
        if ( ihb == H_ATOM )
        {
            Set_End_Index( workspace->hbond_index[i], ihb_top, hbonds );
        }
    }

    Init_Charge_Matrix_Remaining_Entries( system, control, far_nbrs,
            H, H_sp, &Htop, &H_sp_top );

    H->start[system->N_cm] = Htop;
    H_sp->start[system->N_cm] = H_sp_top;

    /* validate lists - decide if reallocation is required! */
    Validate_Lists( workspace, lists,
            data->step, system->N, H->m, Htop, num_bonds, num_hbonds );

#if defined(TEST_FORCES)
    /* Calculate_dBO requires a sorted bonds list */
//    for ( i = 0; i < bonds->n; ++i )
//    {
//        if ( Num_Entries(i, bonds) > 0 )
//        {
//            qsort( &bonds->bond_list[Start_Index(i, bonds)],
//                    Num_Entries(i, bonds), sizeof(bond_data), compare_bonds );
//        }
//    }
#endif

#if defined(DEBUG_FOCUS)
    fprintf( stderr, "step%d: Htop = %d, num_bonds = %d, num_hbonds = %d\n",
             data->step, Htop, num_bonds, num_hbonds );

#endif
}


static void Init_Forces_Tab( reax_system *system, control_params *control,
        simulation_data *data, static_storage *workspace,
        reax_list **lists, output_controls *out_control )
{
    int i, j, pj, target;
    int start_i, end_i;
    int type_i, type_j;
    int Htop, H_sp_top, btop_i, btop_j, num_bonds, num_hbonds;
    int ihb, jhb, ihb_top, jhb_top;
    int flag, flag_sp, val_flag, renbr;
    real r_ij, r2, val;
    real C12, C34, C56;
    real Cln_BOp_s, Cln_BOp_pi, Cln_BOp_pi2;
    real BO, BO_s, BO_pi, BO_pi2;
    sparse_matrix *H, *H_sp;
    reax_list *far_nbrs, *bonds, *hbonds;
    single_body_parameters *sbp_i, *sbp_j;
    two_body_parameters *twbp;
    far_neighbor_data *nbr_pj;
    reax_atom *atom_i, *atom_j;
    bond_data *ibond, *jbond;
    bond_order_data *bo_ij, *bo_ji;

    far_nbrs = lists[FAR_NBRS];
    bonds = lists[BONDS];
    hbonds = lists[HBONDS];
    H = &workspace->H;
    H_sp = &workspace->H_sp;
    Htop = 0;
    H_sp_top = 0;
    num_bonds = 0;
    num_hbonds = 0;
    btop_i = 0;
    btop_j = 0;
    renbr = ((data->step - data->prev_steps) % control->reneighbor) == 0 ? TRUE : FALSE;

    for ( i = 0; i < system->N; ++i )
    {
        atom_i = &system->atoms[i];
        type_i = atom_i->type;
        start_i = Start_Index( i, far_nbrs );
        end_i = End_Index( i, far_nbrs );
        H->start[i] = Htop;
        H_sp->start[i] = H_sp_top;
        btop_i = End_Index( i, bonds );
        sbp_i = &system->reax_param.sbp[type_i];

        if ( control->hbond_cut > 0.0 )
        {
            ihb = sbp_i->p_hbond;
            if ( ihb == H_ATOM )
            {
                ihb_top = End_Index( workspace->hbond_index[i], hbonds );
            }
            else
            {
                ihb_top = -1;
            }
        }
        else
        {
            ihb = NON_H_BONDING_ATOM;
            ihb_top = -1;
        }

        /* update i-j distance - check if j is within cutoff */
        for ( pj = start_i; pj < end_i; ++pj )
        {
            nbr_pj = &far_nbrs->far_nbr_list[pj];
            j = nbr_pj->nbr;
            flag = FALSE;
            flag_sp = FALSE;

#if defined(QMMM)
            if ( system->atoms[i].qmmm_mask == TRUE
                    || system->atoms[j].qmmm_mask == TRUE )
            {
#endif	
            /* check if reneighboring step --
             * atomic distances just computed via
             * Verlet list, so use current distances */
            if ( renbr == TRUE )
            {
                if ( nbr_pj->d <= control->nonb_cut )
                {
                    flag = TRUE;

                    if ( nbr_pj->d <= control->nonb_sp_cut )
                    {
                        flag_sp = TRUE;
                    }
                }
            }
            /* update atomic distances */
            else
            {
                atom_j = &system->atoms[j];
                nbr_pj->d = control->compute_atom_distance( &system->box,
                        atom_i->x, atom_j->x, atom_i->rel_map,
                        atom_j->rel_map, nbr_pj->rel_box,
                        nbr_pj->dvec );

                if ( nbr_pj->d <= control->nonb_cut )
                {
                    flag = TRUE;

                    if ( nbr_pj->d <= control->nonb_sp_cut )
                    {
                        flag_sp = TRUE;
                    }
                }
            }

            if ( flag == TRUE )
            {
                type_j = system->atoms[j].type;
                sbp_j = &system->reax_param.sbp[type_j];
                twbp = &system->reax_param.tbp[type_i][type_j];
                r_ij = nbr_pj->d;

                val = Init_Charge_Matrix_Entry( system, control,
                        workspace, i, j, r_ij, OFF_DIAGONAL );
                val_flag = FALSE;

                for ( target = H->start[i]; target < Htop; ++target )
                {
                    if ( H->j[target] == j )
                    {
                        H->val[target] += val;
                        val_flag = TRUE;
                        break;
                    }
                }

                if ( val_flag == FALSE )
                {
                    H->j[Htop] = j;
                    H->val[Htop] = val;
                    ++Htop;
                }

                /* H_sp matrix entry */
                if ( flag_sp == TRUE )
                {
                    val_flag = FALSE;

                    for ( target = H_sp->start[i]; target < H_sp_top; ++target )
                    {
                        if ( H_sp->j[target] == j )
                        {
                            H_sp->val[target] += val;
                            val_flag = TRUE;
                            break;
                        }
                    }

                    if ( val_flag == FALSE )
                    {
                        H_sp->j[H_sp_top] = j;
                        H_sp->val[H_sp_top] = val;
                        ++H_sp_top;
                    }
                }

#if defined(QMMM)
                if ( system->atoms[i].qmmm_mask == TRUE
                        && system->atoms[j].qmmm_mask == TRUE )
                {
#endif
                /* Only non-dummy atoms can form bonds */
                if ( system->atoms[i].is_dummy == FALSE
                        && system->atoms[j].is_dummy == FALSE )
                {
                    /* hydrogen bond lists */
                    if ( control->hbond_cut > 0.0
                            && (ihb == H_ATOM || ihb == H_BONDING_ATOM)
                            && nbr_pj->d <= control->hbond_cut )
                    {
                        jhb = sbp_j->p_hbond;

                        if ( ihb == H_ATOM && jhb == H_BONDING_ATOM )
                        {
                            hbonds->hbond_list[ihb_top].nbr = j;
                            hbonds->hbond_list[ihb_top].scl = 1;
                            hbonds->hbond_list[ihb_top].ptr = nbr_pj;
                            ++ihb_top;
                            ++num_hbonds;
                        }
                        else if ( ihb == H_BONDING_ATOM && jhb == H_ATOM )
                        {
                            jhb_top = End_Index( workspace->hbond_index[j], hbonds );
                            hbonds->hbond_list[jhb_top].nbr = i;
                            hbonds->hbond_list[jhb_top].scl = -1;
                            hbonds->hbond_list[jhb_top].ptr = nbr_pj;
                            Set_End_Index( workspace->hbond_index[j], jhb_top + 1, hbonds );
                            ++num_hbonds;
                        }
                    }

                    /* uncorrected bond orders */
                    if ( nbr_pj->d <= control->bond_cut )
                    {
                        r2 = SQR( r_ij );

                        if ( sbp_i->r_s > 0.0 && sbp_j->r_s > 0.0 )
                        {
                            C12 = twbp->p_bo1 * POW( r_ij / twbp->r_s, twbp->p_bo2 );
                            BO_s = (1.0 + control->bo_cut) * EXP( C12 );
                        }
                        else
                        {
                            C12 = 0.0;
                            BO_s = 0.0;
                        }

                        if ( sbp_i->r_pi > 0.0 && sbp_j->r_pi > 0.0 )
                        {
                            C34 = twbp->p_bo3 * POW( r_ij / twbp->r_p, twbp->p_bo4 );
                            BO_pi = EXP( C34 );
                        }
                        else
                        {
                            C34 = 0.0;
                            BO_pi = 0.0;
                        }

                        if ( sbp_i->r_pi_pi > 0.0 && sbp_j->r_pi_pi > 0.0 )
                        {
                            C56 = twbp->p_bo5 * POW( r_ij / twbp->r_pp, twbp->p_bo6 );
                            BO_pi2 = EXP( C56 );
                        }
                        else
                        {
                            C56 = 0.0;
                            BO_pi2 = 0.0;
                        }

                        /* Initially BO values are the uncorrected ones, page 1 */
                        BO = BO_s + BO_pi + BO_pi2;

                        if ( BO >= control->bo_cut )
                        {
                            num_bonds += 2;
                            /****** bonds i-j and j-i ******/
                            ibond = &bonds->bond_list[btop_i];
                            btop_j = End_Index( j, bonds );
                            jbond = &bonds->bond_list[btop_j];

                            ibond->nbr = j;
                            jbond->nbr = i;
                            ibond->d = r_ij;
                            jbond->d = r_ij;
                            rvec_Copy( ibond->dvec, nbr_pj->dvec );
                            rvec_Scale( jbond->dvec, -1, nbr_pj->dvec );
                            ivec_Copy( ibond->rel_box, nbr_pj->rel_box );
                            ivec_Scale( jbond->rel_box, -1, nbr_pj->rel_box );
                            ibond->dbond_index = btop_i;
                            jbond->dbond_index = btop_i;
                            ibond->sym_index = btop_j;
                            jbond->sym_index = btop_i;
                            ++btop_i;
                            Set_End_Index( j, btop_j + 1, bonds );

                            bo_ij = &ibond->bo_data;
                            bo_ij->BO = BO;
                            bo_ij->BO_s = BO_s;
                            bo_ij->BO_pi = BO_pi;
                            bo_ij->BO_pi2 = BO_pi2;
                            bo_ji = &jbond->bo_data;
                            bo_ji->BO = BO;
                            bo_ji->BO_s = BO_s;
                            bo_ji->BO_pi = BO_pi;
                            bo_ji->BO_pi2 = BO_pi2;

                            /* Bond Order page2-3, derivative of total bond order prime */
                            Cln_BOp_s = twbp->p_bo2 * C12 / r2;
                            Cln_BOp_pi = twbp->p_bo4 * C34 / r2;
                            Cln_BOp_pi2 = twbp->p_bo6 * C56 / r2;

                            /* Only dln_BOp_xx wrt. dr_i is stored here, note that
                               dln_BOp_xx/dr_i = -dln_BOp_xx/dr_j and all others are 0 */
                            rvec_Scale( bo_ij->dln_BOp_s, -bo_ij->BO_s * Cln_BOp_s, ibond->dvec );
                            rvec_Scale( bo_ij->dln_BOp_pi, -bo_ij->BO_pi * Cln_BOp_pi, ibond->dvec );
                            rvec_Scale( bo_ij->dln_BOp_pi2,
                                    -bo_ij->BO_pi2 * Cln_BOp_pi2, ibond->dvec );
                            rvec_Scale( bo_ji->dln_BOp_s, -1., bo_ij->dln_BOp_s );
                            rvec_Scale( bo_ji->dln_BOp_pi, -1., bo_ij->dln_BOp_pi );
                            rvec_Scale( bo_ji->dln_BOp_pi2, -1., bo_ij->dln_BOp_pi2 );

                            /* Only dBOp wrt. dr_i is stored here, note that
                               dBOp/dr_i = -dBOp/dr_j and all others are 0 */
                            rvec_Scale( bo_ij->dBOp, -(bo_ij->BO_s * Cln_BOp_s
                                        + bo_ij->BO_pi * Cln_BOp_pi
                                        + bo_ij->BO_pi2 * Cln_BOp_pi2), ibond->dvec );
                            rvec_Scale( bo_ji->dBOp, -1., bo_ij->dBOp );

                            rvec_Add( workspace->dDeltap_self[i], bo_ij->dBOp );
                            rvec_Add( workspace->dDeltap_self[j], bo_ji->dBOp );

                            bo_ij->BO_s -= control->bo_cut;
                            bo_ij->BO -= control->bo_cut;
                            bo_ji->BO_s -= control->bo_cut;
                            bo_ji->BO -= control->bo_cut;
                            workspace->total_bond_order[i] += bo_ij->BO;
                            workspace->total_bond_order[j] += bo_ji->BO;
                            bo_ij->Cdbo = 0.0;
                            bo_ij->Cdbopi = 0.0;
                            bo_ij->Cdbopi2 = 0.0;
                            bo_ji->Cdbo = 0.0;
                            bo_ji->Cdbopi = 0.0;
                            bo_ji->Cdbopi2 = 0.0;

                            Set_End_Index( j, btop_j + 1, bonds );
                        }
                    }
                }
#if defined(QMMM)
                }
#endif
            }
#if defined(QMMM)
            }
#endif
        }

        /* diagonal entry */
        H->j[Htop] = i;
        H->val[Htop] = Init_Charge_Matrix_Entry( system, control,
                workspace, i, i, r_ij, DIAGONAL );
        ++Htop;

        H_sp->j[H_sp_top] = i;
        H_sp->val[H_sp_top] = H->val[Htop - 1];
        ++H_sp_top;

        Set_End_Index( i, btop_i, bonds );
        if ( ihb == H_ATOM )
        {
            Set_End_Index( workspace->hbond_index[i], ihb_top, hbonds );
        }
    }

    Init_Charge_Matrix_Remaining_Entries( system, control, far_nbrs,
            H, H_sp, &Htop, &H_sp_top );

    H->start[system->N_cm] = Htop;
    H_sp->start[system->N_cm] = H_sp_top;

    /* validate lists - decide if reallocation is required! */
    Validate_Lists( workspace, lists,
            data->step, system->N, H->m, Htop, num_bonds, num_hbonds );

#if defined(DEBUG_FOCUS)
    fprintf( stderr, "step%d: Htop = %d, num_bonds = %d, num_hbonds = %d\n",
             data->step, Htop, num_bonds, num_hbonds );
    //Print_Bonds( system, bonds, "sbonds.out" );
    //Print_Bond_List2( system, bonds, "sbonds.out" );
    //Print_Sparse_Matrix2( H, "H.out", NULL );
#endif
}


void Estimate_Storage_Sizes( reax_system *system, control_params *control,
        reax_list **lists, int *Htop, int *hb_top, int *bond_top, int *num_3body )
{
    int i, j, pj;
    int start_i, end_i;
    int type_i, type_j;
    int ihb, jhb;
    real r_ij;
    real C12, C34, C56;
    real BO, BO_s, BO_pi, BO_pi2;
    reax_list *far_nbrs;
    single_body_parameters *sbp_i, *sbp_j;
    two_body_parameters *twbp;
    far_neighbor_data *nbr_pj;
    reax_atom *atom_i, *atom_j;

    far_nbrs = lists[FAR_NBRS];

    for ( i = 0; i < far_nbrs->n; ++i )
    {
        atom_i = &system->atoms[i];
        type_i = atom_i->type;
        start_i = Start_Index( i, far_nbrs );
        end_i = End_Index( i, far_nbrs );
        sbp_i = &system->reax_param.sbp[type_i];
        ihb = sbp_i->p_hbond;

        /* update i-j distance - check if j is within cutoff */
        for ( pj = start_i; pj < end_i; ++pj )
        {
            nbr_pj = &far_nbrs->far_nbr_list[pj];

            if ( nbr_pj->d <= control->nonb_cut )
            {
                j = nbr_pj->nbr;
                atom_j = &system->atoms[j];
                type_j = atom_j->type;
                sbp_j = &system->reax_param.sbp[type_j];
                twbp = &system->reax_param.tbp[type_i][type_j];
                ++(*Htop);

                /* hydrogen bond lists */
                if ( control->hbond_cut > 0.0
                        && (ihb == H_ATOM || ihb == H_BONDING_ATOM)
                        && nbr_pj->d <= control->hbond_cut )
                {
                    jhb = sbp_j->p_hbond;

                    if ( ihb == H_ATOM && jhb == H_BONDING_ATOM )
                    {
                        ++hb_top[i];
                    }
                    else if ( ihb == H_BONDING_ATOM && jhb == H_ATOM )
                    {
                        ++hb_top[j];
                    }
                }

                /* uncorrected bond orders */
                if ( nbr_pj->d <= control->bond_cut )
                {
                    r_ij = nbr_pj->d;

                    if ( sbp_i->r_s > 0.0 && sbp_j->r_s > 0.0 )
                    {
                        C12 = twbp->p_bo1 * POW( r_ij / twbp->r_s, twbp->p_bo2 );
                        BO_s = (1.0 + control->bo_cut) * EXP( C12 );
                    }
                    else
                    {
                        C12 = 0.0;
                        BO_s = 0.0;
                    }

                    if ( sbp_i->r_pi > 0.0 && sbp_j->r_pi > 0.0 )
                    {
                        C34 = twbp->p_bo3 * POW( r_ij / twbp->r_p, twbp->p_bo4 );
                        BO_pi = EXP( C34 );
                    }
                    else
                    {
                        C34 = 0.0;
                        BO_pi = 0.0;
                    }

                    if ( sbp_i->r_pi_pi > 0.0 && sbp_j->r_pi_pi > 0.0 )
                    {
                        C56 = twbp->p_bo5 * POW( r_ij / twbp->r_pp, twbp->p_bo6 );
                        BO_pi2 = EXP( C56 );
                    }
                    else
                    {
                        C56 = 0.0;
                        BO_pi2 = 0.0;
                    }

                    /* Initially BO values are the uncorrected ones, page 1 */
                    BO = BO_s + BO_pi + BO_pi2;

                    if ( BO >= control->bo_cut )
                    {
                        ++bond_top[i];
                        ++bond_top[j];
                    }
                }
            }
        }
    }

    *Htop += system->N;
    *Htop *= SAFE_ZONE;
    for ( i = 0; i < system->N; ++i )
    {
        hb_top[i] = MAX( hb_top[i] * SAFE_HBONDS, MIN_HBONDS );
        *num_3body += SQR( bond_top[i] );
        bond_top[i] = MAX( bond_top[i] * 2, MIN_BONDS );
    }
    *num_3body *= SAFE_ZONE;
}


void Compute_Forces( reax_system *system, control_params *control,
        simulation_data *data, static_storage *workspace,
        reax_list** lists, output_controls *out_control, int realloc )
{
    real t_start, t_elapsed;

    t_start = Get_Time( );
    if ( control->tabulate <= 0 )
    {
        Init_Forces( system, control, data, workspace, lists, out_control );
    }
    else
    {
        Init_Forces_Tab( system, control, data, workspace, lists, out_control );
    }
    t_elapsed = Get_Timing_Info( t_start );
    data->timing.init_forces += t_elapsed;

    t_start = Get_Time( );
    Compute_Bonded_Forces( system, control, data, workspace, lists, out_control );
    t_elapsed = Get_Timing_Info( t_start );
    data->timing.bonded += t_elapsed;

    t_start = Get_Time( );
    Compute_NonBonded_Forces( system, control, data, workspace,
            lists, out_control, realloc );
    t_elapsed = Get_Timing_Info( t_start );
    data->timing.nonb += t_elapsed;

    Compute_Total_Force( system, control, data, workspace, lists );

#if defined(DEBUG_FOCUS)
    //Print_Total_Force( system, control, data, workspace, lists, out_control );
#endif

#if defined(TEST_FORCES)
    Print_Total_Force( system, control, data, workspace, lists, out_control );
    Compare_Total_Forces( system, control, data, workspace, lists, out_control );
#endif
}