/*
    Copyright (C) 1998  Dennis Roddeman
    email: dennis.roddeman@uibk.ac.at

    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.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software Foundation 
    59 Temple Place, Suite 330, Boston, MA, 02111-1307, USA
*/

#include "tochnog.h"

#define MAX_ITER 1000
#define EPS_F 1.e-6
#define EPS_EPS_F 1.e-3
#define EPS_SIZE 1.e-5
#define EPS_DAMAGE 1.e-5
#define EPS_TMP 1.e-1

void set_stress( long int element, long int gr, double coord_ip[],
  double old_unknowns[], double new_unknowns[], 
  double old_grad_old_unknowns[], double new_grad_new_unknowns[], 
  double rotated_old_sig[], double new_sig[],
  double rotated_old_msig[], double new_msig[], 
  double inc_ept[], double old_epe[], 
  double inc_epe[], double inc_epp[], 
  double old_rho[], double new_rho[],
  double old_hisv[], double new_hisv[], 
  double old_damage, double &new_damage, 
  double old_kappa, double &new_kappa, 
  double ddsdde[] )

  // Solid materials.

{

  long int i=0, j=0, ind_ddsdde=0, plasti_found=0, 
    plasti_iter=0, membrane_found=0, membrane_iter=0,
    swit=0, membrane=-NO, viscoplasti=0, plasti_type=-NONE, total_plasti_iter=0, 
    nuser_data=0, idim=0, jdim=0, kdim=0, ldim=0, ldum=0, idum[1], task[2];
  double lambda=0., deps_size=0., lambda_new=0., lambda_previous=0., 
    tmp=0., f=0., f_previous=0, f_ref=0., materi_expansion_linear=0., eta=0., p=0.,
    inc_pres=0., plasti_kinematic_hardening=0., young = 0., poisson=0., 
    compressibility=0., fac=0., rdum=0., ddum[MDIM*MDIM], 
    memmat[MDIM][MDIM], elasti_transverse_isotropy[DATA_ITEM_SIZE],
    temperature_strain[MDIM*MDIM], plasti_dir[MDIM*MDIM], plasti_visco[DATA_ITEM_SIZE], 
    inc_rho[MDIM*MDIM], test_sig[MDIM*MDIM], total_inc_epp[MDIM*MDIM], 
    work_inc_epp[MDIM*MDIM], new_epe[MDIM*MDIM], rotated_old_sig_undamaged[MDIM*MDIM],
    C[MDIM][MDIM][MDIM][MDIM], Cmem[MDIM][MDIM][MDIM][MDIM], 
    Cuser[MDIM][MDIM][MDIM][MDIM], user_data[DATA_ITEM_SIZE];

  swit = set_swit(element,-1, "set_stress");
  if ( swit ) pri( "In routine SET_STRESS" );

  if ( get_group_data( GROUP_MATERI_PLASTI_VISCO, gr, new_unknowns, 
      plasti_visco, ldum, GET_IF_EXISTS ) ) {
    viscoplasti = 1;
    eta = plasti_visco[0]; p = plasti_visco[1];
    f_ref = plasti_visco[2]; assert( f_ref!=0. );
  }

  get_group_data( GROUP_MATERI_ELASTI_COMPRESSIBILITY, gr, new_unknowns, 
    &compressibility, ldum, GET_IF_EXISTS );
  db( GROUP_MATERI_MEMBRANE, gr, &membrane, ddum, ldum, VERSION_NORMAL, GET_IF_EXISTS );
  task[1] = membrane;
  if ( get_group_data( GROUP_MATERI_ELASTI_TRANSVERSE_ISOTROPY, gr, new_unknowns,
      elasti_transverse_isotropy, ldum, GET_IF_EXISTS ) ) {
    task[0] = GROUP_MATERI_ELASTI_TRANSVERSE_ISOTROPY;
    task[1] = -NO;
    C_matrix( young, poisson, compressibility, elasti_transverse_isotropy, C, task );
    task[1] = membrane;
    C_matrix( young, poisson, compressibility, elasti_transverse_isotropy, Cmem, task );
  }
  else if ( get_group_data( GROUP_MATERI_ELASTI_YOUNG, gr, new_unknowns, 
      &young, ldum, GET_IF_EXISTS ) ) {
    get_group_data( GROUP_MATERI_ELASTI_POISSON, gr, new_unknowns, &poisson, ldum, GET_IF_EXISTS );
    task[0] = GROUP_MATERI_ISOTROPY;
    task[1] = -NO;
    C_matrix( young, poisson, compressibility, elasti_transverse_isotropy, C, task );
    task[1] = membrane;
    C_matrix( young, poisson, compressibility, elasti_transverse_isotropy, Cmem, task );
  }
  else {
    task[0] = GROUP_MATERI_ISOTROPY;
    task[1] = -NO;
    C_matrix( young, poisson, compressibility, elasti_transverse_isotropy, C, task );
    task[1] = membrane;
    C_matrix( young, poisson, compressibility, elasti_transverse_isotropy, Cmem, task );
  }

  if ( groundflow_pressure ) inc_pres = new_unknowns[pres_indx] - 
    old_unknowns[pres_indx];

  db( GROUP_MATERI_PLASTI_KINEMATIC_HARDENING, gr, idum, &plasti_kinematic_hardening, 
    ldum, VERSION_NORMAL, GET_IF_EXISTS );
  get_group_data( GROUP_USER_DATA, gr, new_unknowns, user_data, nuser_data, GET_IF_EXISTS );

  if ( condif_temperature ) {
    array_set( temperature_strain, 0., MDIM*MDIM );
    get_group_data( GROUP_MATERI_EXPANSION_LINEAR, gr, new_unknowns, 
      &materi_expansion_linear, ldum, GET_IF_EXISTS );
    for ( idim=0; idim<MDIM; idim++ ) temperature_strain[idim*MDIM+idim] = 
      -materi_expansion_linear * ( new_unknowns[temp_indx] - old_unknowns[temp_indx] );
    if ( swit ) pri( "temperature_strain", temperature_strain, MDIM, MDIM );
  }

  array_set( ddsdde, 0., MSTRAIN*MSTRAIN );
  array_set( &Cuser[0][0][0][0], 0., MDIM*MDIM*MDIM*MDIM );
  array_set( plasti_dir, 0., MDIM*MDIM );
  array_set( total_inc_epp, 0., MDIM*MDIM );
  deps_size = array_size( inc_ept, MDIM*MDIM );
  if ( deps_size<EPS_SIZE ) deps_size = EPS_SIZE;

  while ( !plasti_found && total_plasti_iter<MAX_ITER ) {
    plasti_iter++;
    total_plasti_iter++;
    if ( swit ) {
      pri( "plasti_iter", plasti_iter );
      pri( "total_plasti_iter", total_plasti_iter );
      pri( "lambda", lambda );
    }
      // plastic strain part in elastic-plastic strain part
    if ( viscoplasti )
      array_multiply( plasti_dir, work_inc_epp, lambda, MDIM*MDIM );
    else
      array_multiply( plasti_dir, work_inc_epp, lambda*deps_size, MDIM*MDIM );
    array_add( work_inc_epp, total_inc_epp, inc_epp, MDIM*MDIM );
    if ( swit ) pri( "plastic strain increment", inc_epp, MDIM, MDIM );
    if ( materi_plasti_kappa ) {
      tmp = array_inproduct( inc_epp, inc_epp, MDIM*MDIM );
      new_kappa = old_kappa + sqrt(0.5*tmp);     
      if ( swit ) pri( "new_kappa", new_kappa );
    }
      // plasti_rho
    if ( materi_plasti_rho ) {
      array_multiply( inc_epp, inc_rho, 
        plasti_kinematic_hardening, MDIM*MDIM );
      array_add( old_rho, inc_rho, new_rho, MDIM*MDIM );
      if ( swit ) pri( "new_rho", new_rho, MDIM, MDIM );
    }
      // elastic strain part in elastic-plastic strain part
    array_subtract( inc_ept, inc_epp, inc_epe, MDIM*MDIM );
    if ( condif_temperature ) array_add( inc_epe, temperature_strain, 
      inc_epe, MDIM*MDIM );
    if ( swit ) pri( "inc_epe", inc_epe, MDIM, MDIM );
    membrane_found = membrane_iter = 0;
    if ( materi_damage ) {
      if ( old_damage>(1.-EPS_DAMAGE) )
        array_set( rotated_old_sig_undamaged, 0., MDIM*MDIM );
      else
        array_multiply( rotated_old_sig, rotated_old_sig_undamaged, 
          1./(1.-old_damage), MDIM*MDIM );
    }
    else
      array_move( rotated_old_sig, rotated_old_sig_undamaged, MDIM*MDIM );
    while ( !membrane_found && membrane_iter<MAX_ITER ) {
      membrane_iter++;
      if ( swit ) {
        pri( "membrane_iter", membrane_iter );
        pri( "inc_epe", inc_epe, MDIM, MDIM );
      }
        // total elastic strain
      if ( materi_strain_elasti ) {
        array_add( old_epe, inc_epe, new_epe, MDIM*MDIM );
        if ( swit ) pri( "new_epe", new_epe, MDIM, MDIM );
      }
        // elasticity
      matrix_a4b( C, inc_epe, new_sig );
      if ( swit ) pri( "stress after elasticity", new_sig, MDIM, MDIM );
      user_sigma( user_data, new_unknowns, inc_epe, new_epe, 
        old_hisv, new_hisv, rotated_old_sig_undamaged, new_sig, Cuser );
      stress_umat( element, gr, nuser_data, user_data, coord_ip,
        old_hisv, new_hisv, old_unknowns, new_unknowns,
        old_epe, inc_epe, rotated_old_sig_undamaged, new_sig, ddsdde );
      memmat[0][0] = C[0][0][0][0];
      memmat[0][1] = C[0][0][1][1];
      memmat[0][2] = C[0][0][2][2];
      memmat[1][0] = C[1][1][0][0];
      memmat[1][1] = C[1][1][1][1];
      memmat[1][2] = C[1][1][2][2];
      memmat[2][0] = C[2][2][0][0];
      memmat[2][1] = C[2][2][1][1];
      memmat[2][2] = C[2][2][2][2];
      if ( swit ) pri( "incremental stress after elasticity", new_sig, MDIM, MDIM );
        // hyperelasticity
      hyperelasticity( element, gr, old_unknowns, new_unknowns, old_epe, 
        new_epe, new_sig );
      if ( swit ) pri( "incremental stress after hyperelasticity", 
        new_sig, MDIM, MDIM );
        // viscosity
      viscous_stress( element, gr, user_data, old_unknowns, new_unknowns,
        old_grad_old_unknowns, new_grad_new_unknowns, new_sig );
      if ( swit ) pri( "incremental stress after viscosity", new_sig, MDIM, MDIM );
        // viscoelasticity
      visco_elasticity( element, gr, new_unknowns, inc_epe, rotated_old_msig,
        new_sig, new_msig, memmat );
      if ( swit ) pri( "incremental stress after visco elasticity", 
        new_sig, MDIM, MDIM );
      for ( idim=0; idim<MDIM; idim++ ) new_sig[idim*MDIM+idim] -= inc_pres;
      if ( swit ) pri( "incremental stress after pressure", new_sig, MDIM, MDIM );
        // memory
      array_add( rotated_old_sig_undamaged, new_sig, new_sig, MDIM*MDIM );
      if ( swit ) pri( "stress after memory", new_sig, MDIM, MDIM );
        // damage
      if ( materi_damage ) {
        damage( gr, new_epe, new_sig, old_damage, new_damage );
        array_multiply( new_sig, new_sig, 1.-new_damage, MDIM*MDIM );
        array_multiply( &memmat[0][0], &memmat[0][0], 1.-new_damage, MDIM*MDIM );
        if ( swit ) pri( "stress after damage", new_sig, MDIM, MDIM );
      }
          // membrane?
      membrane_found = membrane_apply( element, gr, memmat, inc_ept,
        inc_epe, new_sig );
    }
      // test stresses
    array_move( new_sig, test_sig, MDIM*MDIM );
    if ( materi_plasti_rho ) 
      array_subtract( test_sig, new_rho, test_sig, MDIM*MDIM );
      // new lambda
    if ( plasti_iter==1 ) {
      plasti_type = -NONE; plasti_rule( element, gr, user_data, 
        new_unknowns, new_grad_new_unknowns, old_hisv, new_hisv,
        GET_YIELD_RULE, plasti_type, test_sig, f, ddum );
      if ( swit ) {
        pri( "plasti_iter", plasti_iter );
        pri( "plasti_type", plasti_type );
        pri( "f", f );
      }
      if ( f<EPS_F ) 
        plasti_found = 1;
      else {
        if ( viscoplasti ) {
          lambda_previous = 0.; f_previous = f;
          lambda = eta * pow(f/f_ref,p);
        }
        else {
          lambda_previous = 0.; f_previous = f;
          lambda = 1.e-3;
        }
        plasti_rule( element, gr, user_data, new_unknowns, new_grad_new_unknowns,
          old_hisv, new_hisv, GET_FLOW_RULE_GRAD, plasti_type, 
          test_sig, rdum, plasti_dir );
        if ( swit ) pri( "plasti_dir", plasti_dir, MDIM*MDIM );
      }
    }
    else {
      plasti_rule( element, gr, user_data, new_unknowns, new_grad_new_unknowns,
        old_hisv, new_hisv, GET_YIELD_RULE, plasti_type, test_sig, f, ddum );
      if ( swit ) {
        pri( "plasti_iter", plasti_iter );
        pri( "lambda", lambda );
        pri( "f", f );
      }
      if ( scalar_dabs(f)<EPS_F || scalar_dabs(f-f_previous)<EPS_EPS_F*EPS_F ) {
        array_add( work_inc_epp, total_inc_epp, total_inc_epp, MDIM*MDIM );
        plasti_type = -NONE; plasti_rule( element, gr, user_data, 
          new_unknowns, new_grad_new_unknowns, old_hisv, new_hisv,
          GET_YIELD_RULE, plasti_type, test_sig, f, ddum );
        if ( scalar_dabs(f)<EPS_F ) 
          plasti_found = 1;
        else {
          plasti_iter = 0; lambda = 0.;
        }
      }
      else if ( viscoplasti ) {
        lambda_previous = lambda; f_previous = f;
        lambda = eta * pow(f/f_ref,p);
      }
      else {
        if ( f>f_previous )
          lambda = ( lambda + lambda_previous ) / 2.; // smaller steps if f becomes larger
        else {
          tmp = - (lambda-lambda_previous)*f_previous/(f-f_previous);
	  if ( deps_size>EPS_SIZE ) {
            if ( tmp>EPS_TMP ) tmp = EPS_TMP;
            if ( tmp<-EPS_TMP ) tmp = -EPS_TMP;
          }
          lambda_new = lambda_previous + tmp;
          lambda_previous = lambda; f_previous = f;
          lambda = lambda_new;
        }
      }
    }
  }

    // fill material stiffness, we use the linear stiffness
  for ( idim=0; idim<MDIM; idim++ ) {
    for ( jdim=idim; jdim<MDIM; jdim++ ) {
      for ( kdim=0; kdim<MDIM; kdim++ ) {
        for ( ldim=kdim; ldim<MDIM; ldim++ ) {
          if ( kdim==ldim ) 
            fac = 1.0;
          else 
            fac = 0.5;
          i = stress_indx(idim,jdim);
          j = stress_indx(kdim,ldim);
          ind_ddsdde = i*MSTRAIN+j;
          ddsdde[ind_ddsdde] += fac * ( Cmem[idim][jdim][kdim][ldim]
            + Cuser[idim][jdim][kdim][ldim] );
        }
      }
    }
  }

  if ( swit ) {
    pri( "plasti_found", plasti_found );
    pri( "membrane_found", membrane_found );
    pri( "stress", new_sig, MDIM, MDIM );
    pri( "ddsdde", ddsdde, MSTRAIN, MSTRAIN );
    pri( "Out routine SET_STRESS" );
  }

}

void stress_umat( long int element, long int gr,
  long int nuser_data, double user_data[], double coord_ip[],
  double old_hisv[], double new_hisv[], 
  double old_unknowns[], double new_unknowns[], 
  double old_epe[], double inc_epe[], 
  double rotated_old_sig[], double new_sig[], double ddsdde[] )

  /* Interface routine to umat routine. */

{

  double stress[MSTRAIN], statev[DATA_ITEM_SIZE], 
   sse[1], spd[1], scd[1], rpl[1], ddsddt[1], drplde[1], drpldt[1], 
   stran[MSTRAIN], dstran[MSTRAIN], time[1], dtime[1], temp[1], dtemp[1], 
   predef[1], dpred[1];
  char cmname[1];
  long int ndi[1], nshr[1], ntens[1], nstatv[1];
  double props[DATA_ITEM_SIZE];
  long int nprops[1];
  double coords[MDIM], drot[1], pnewdt[1], celent[1], dfgrd0[1], dfgrd1[1];
  long int noel[1], npt[1], layer[1], kspt[1], kstep[1], kinc[1];
  short cmname_len;

  long int icontrol=0, number_of_iterations=0, ldum=0, user_umat=-NO, idum[1];
  long int istrain=0, jstrain=0, idim=0, jdim=0, kdim=0, ldim=0, i=0, j=0, 
    ind_ddsdde=0, abaqus_indx[MSTRAIN][2];
  double ddum[1], ddsdde_tmp[MSTRAIN*MSTRAIN];

  db( NUMBER_ITERATIONS, 0, &number_of_iterations, ddum, ldum, VERSION_NORMAL, GET );
  db( ICONTROL, 0, &icontrol, ddum, ldum, VERSION_NORMAL, GET );
  db( GROUP_USER_UMAT, gr, &user_umat, ddum, ldum, VERSION_NORMAL, GET_IF_EXISTS );
  if ( user_umat==-YES ) {

      // dummies

    drot[0] = 0.;
    pnewdt[0] = 0.;
    celent[0] = 0.;
    dfgrd0[0] = 0.;
    dfgrd1[0] = 0.;
    sse[0] = 0.;
    spd[0] = 0.;
    scd[0] = 0.;
    rpl[0] = 0.;
    ddsddt[0] = 0.;
    drplde[0]= 0.;
    drpldt[0] = 0.;
    predef[0] = 0.;
    dpred[0] = 0.;
    npt[0] = 0;
    layer[0] = 0;
    kspt[0] = 0;
    kstep[0] = icontrol;
    kinc[0] = number_of_iterations;
    cmname_len = 0;

      // real data

    array_move( coord_ip, coords, ndim );

    noel[0] = element;

    db( DTIME, 0, idum, dtime, ldum, VERSION_NEW, GET );
    db( TIME_CURRENT, 0, idum, time, ldum, VERSION_NORMAL, GET );

    if ( condif_temperature ) {
      temp[0] = old_unknowns[temp_indx];
      dtemp[0] = new_unknowns[temp_indx] - old_unknowns[temp_indx];
    }
    else {
      temp[0] = 0.;
      dtemp[0] = 0.;
    }

    nprops[0] = nuser_data;
    nstatv[0] = materi_history_variables;
    ndi[0] = 3;
    nshr[0] = 3;
    ntens[0] = 6;

    array_move( user_data, props, nprops[0] );

    array_move( old_hisv, statev, nstatv[0] );

    stress[0] = rotated_old_sig[0];
    stress[1] = rotated_old_sig[4];
    stress[2] = rotated_old_sig[8];
    stress[3] = rotated_old_sig[1];
    stress[4] = rotated_old_sig[2];
    stress[5] = rotated_old_sig[5];

    array_set( ddsdde_tmp, 0., MSTRAIN*MSTRAIN );

    stran[0] = old_epe[0];
    stran[1] = old_epe[4];
    stran[2] = old_epe[8];
    stran[3] = old_epe[1];
    stran[4] = old_epe[2];
    stran[5] = old_epe[5];

    dstran[0] = inc_epe[0];
    dstran[1] = inc_epe[4];
    dstran[2] = inc_epe[8];
    dstran[3] = inc_epe[1];
    dstran[4] = inc_epe[2];
    dstran[5] = inc_epe[5];

      // stress contribution by umat
    umat_( stress, statev, ddsdde_tmp, sse, spd, scd, rpl, ddsddt,
      drplde, drpldt, stran, dstran, time, dtime, temp, dtemp, predef, 
      dpred, cmname, ndi, nshr, ntens, nstatv, props, nprops, coords, drot, 
      pnewdt, celent, dfgrd0, dfgrd1, noel, npt, layer, 
      kspt, kstep, kinc, cmname_len );

      // from abaqus fortran to tochnog c++
    abaqus_indx[0][0] = 0;
    abaqus_indx[0][1] = 0;
    abaqus_indx[1][0] = 1;
    abaqus_indx[1][1] = 1;
    abaqus_indx[2][0] = 2;
    abaqus_indx[2][1] = 2;
    abaqus_indx[3][0] = 0;
    abaqus_indx[3][1] = 1;
    abaqus_indx[4][0] = 0;
    abaqus_indx[4][1] = 2;
    abaqus_indx[5][0] = 1;
    abaqus_indx[5][1] = 2;
    for ( istrain=0; istrain<MSTRAIN; istrain++ ) {
      idim = abaqus_indx[istrain][0];
      jdim = abaqus_indx[istrain][1];
      i = stress_indx(idim,jdim); 
      for ( jstrain=0; jstrain<MSTRAIN; jstrain++ ) {
        kdim = abaqus_indx[jstrain][0];
        ldim = abaqus_indx[jstrain][1];
        j = stress_indx(kdim,ldim); 
        ind_ddsdde = i*MSTRAIN+j;
        ddsdde[ind_ddsdde] += ddsdde_tmp[jstrain*MSTRAIN+istrain];
      }
    }

    array_move( statev, new_hisv, nstatv[0] );

    new_sig[0] += ( stress[0] - rotated_old_sig[0] ) ;
    new_sig[4] += ( stress[1] - rotated_old_sig[4] );
    new_sig[8] += ( stress[2] - rotated_old_sig[8] );
    new_sig[1] = new_sig[3] += ( stress[3] - rotated_old_sig[1] );
    new_sig[2] = new_sig[6] += ( stress[4] - rotated_old_sig[2] );
    new_sig[5] = new_sig[7] += ( stress[5] - rotated_old_sig[5] );

  }
  
}
