// clang-format off
/* ----------------------------------------------------------------------
   LAMMPS - Large-scale Atomic/Molecular Massively Parallel Simulator
   https://www.lammps.org/, Sandia National Laboratories
   LAMMPS development team: developers@lammps.org

   Copyright (2003) Sandia Corporation.  Under the terms of Contract
   DE-AC04-94AL85000 with Sandia Corporation, the U.S. Government retains
   certain rights in this software.  This software is distributed under
   the GNU General Public License.

   See the README file in the top-level LAMMPS directory.
------------------------------------------------------------------------- */

/* ----------------------------------------------------------------------
   Contributing author: Joel Clemmer (SNL)
------------------------------------------------------------------------- */

#include "fix_deform_pressure.h"

#include "comm.h"
#include "compute.h"
#include "domain.h"
#include "error.h"
#include "group.h"
#include "input.h"
#include "irregular.h"
#include "math_const.h"
#include "modify.h"
#include "update.h"
#include "variable.h"

#include <cmath>
#include <cstring>

using namespace LAMMPS_NS;
using namespace FixConst;
using namespace MathConst;

enum { NOCOUPLE = 0, XYZ, XY, YZ, XZ };

/* ---------------------------------------------------------------------- */

FixDeformPressure::FixDeformPressure(LAMMPS *lmp, int narg, char **arg) :
  FixDeform(lmp, narg, arg), id_temp(nullptr), id_press(nullptr), temperature(nullptr),
  pressure(nullptr)
{
  // set defaults

  set_extra = new SetExtra[7];
  memset(set_extra, 0, 7 * sizeof(SetExtra));
  memset(&set_box, 0, sizeof(Set));

  // parse only parameter/style arguments specific to this child class

  int index, iarg;
  std::size_t i = 0;
  while (i < leftover_iarg.size()) {
    iarg = leftover_iarg[i];
    if (strcmp(arg[iarg], "x") == 0 ||
        strcmp(arg[iarg], "y") == 0 ||
        strcmp(arg[iarg], "z") == 0) {

      if (strcmp(arg[iarg], "x") == 0) index = 0;
      else if (strcmp(arg[iarg], "y") == 0) index = 1;
      else if (strcmp(arg[iarg], "z") == 0) index = 2;

      if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure", error);
      if (strcmp(arg[iarg + 1], "pressure") == 0) {
        if (iarg + 4 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure pressure", error);
        set[index].style = PRESSURE;
        if (strstr(arg[iarg + 2], "v_") != arg[iarg + 2]) {
          set_extra[index].ptarget = utils::numeric(FLERR, arg[iarg + 2], false, lmp);
        } else {
          set_extra[index].pstr = utils::strdup(&arg[iarg + 2][2]);
          set_extra[index].pvar_flag = 1;
        }
        set_extra[index].pgain = utils::numeric(FLERR, arg[iarg + 3], false, lmp);
        i += 4;
      } else if (strcmp(arg[iarg + 1], "pressure/mean") == 0) {
        if (iarg + 4 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure pressure/mean", error);
        set[index].style = PMEAN;
        if (strstr(arg[iarg + 2], "v_") != arg[iarg + 2]) {
          set_extra[index].ptarget = utils::numeric(FLERR, arg[iarg + 2], false, lmp);
        } else {
          set_extra[index].pstr = utils::strdup(&arg[iarg + 2][2]);
          set_extra[index].pvar_flag = 1;
        }
        set_extra[index].pgain = utils::numeric(FLERR, arg[iarg + 3], false, lmp);
        i += 4;
      } else error->all(FLERR, "Illegal fix deform/pressure command argument: {}", arg[iarg + 1]);

    } else if (strcmp(arg[iarg], "xy") == 0 ||
               strcmp(arg[iarg], "xz") == 0 ||
               strcmp(arg[iarg], "yz") == 0) {

      if (strcmp(arg[iarg], "xy") == 0) index = 5;
      else if (strcmp(arg[iarg], "xz") == 0) index = 4;
      else if (strcmp(arg[iarg], "yz") == 0) index = 3;

      if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure", error);
      if (strcmp(arg[iarg + 1], "pressure") == 0) {
        if (iarg + 4 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure pressure", error);
        set[index].style = PRESSURE;
        if (strstr(arg[iarg + 2], "v_") != arg[iarg + 2]) {
          set_extra[index].ptarget = utils::numeric(FLERR, arg[iarg + 2], false, lmp);
        } else {
          set_extra[index].pstr = utils::strdup(&arg[iarg + 2][2]);
          set_extra[index].pvar_flag = 1;
        }
        set_extra[index].pgain = utils::numeric(FLERR, arg[iarg + 3], false, lmp);
        i += 4;
      } else if (strcmp(arg[iarg + 1], "erate/rescale") == 0) {
        if (iarg + 3 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure erate/rescale", error);
        set[index].style = ERATERS;
        set[index].rate = utils::numeric(FLERR, arg[iarg + 2], false, lmp);
        i += 3;
      } else error->all(FLERR, "Illegal fix deform/pressure command: {}", arg[iarg + 1]);

    } else if (strcmp(arg[iarg], "box") == 0) {
      if (strcmp(arg[iarg + 1], "volume") == 0) {
        set_box.style = VOLUME;
        i += 2;
      } else if (strcmp(arg[iarg + 1], "pressure") == 0) {
        if (iarg + 4 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure pressure", error);
        set_box.style = PRESSURE;
        if (strstr(arg[iarg + 2], "v_") != arg[iarg + 2]) {
          set_extra[6].ptarget = utils::numeric(FLERR, arg[iarg + 2], false, lmp);
        } else {
          set_extra[6].pstr = utils::strdup(&arg[iarg + 2][2]);
          set_extra[6].pvar_flag = 1;
        }
        set_extra[6].pgain = utils::numeric(FLERR, arg[iarg + 3], false, lmp);
        i += 4;
      } else error->all(FLERR, "Illegal fix deform/pressure command argument: {}", arg[iarg + 1]);
    } else break;
  }

  // read options from end of input line
  // shift arguments before reading

  iarg = iarg_options_start;
  options(i, narg - iarg, &arg[iarg]);

  // repeat: setup dimflags used by other classes to check for volume-change conflicts

  for (int i = 0; i < 6; i++)
    if (set[i].style == NONE) dimflag[i] = 0;
    else dimflag[i] = 1;

  if (set_box.style != NONE) {
    dimflag[0] = 1;
    dimflag[1] = 1;
    dimflag[2] = 1;
  }

  if (dimflag[0]) box_change |= BOX_CHANGE_X;
  if (dimflag[1]) box_change |= BOX_CHANGE_Y;
  if (dimflag[2]) box_change |= BOX_CHANGE_Z;
  if (dimflag[3]) box_change |= BOX_CHANGE_YZ;
  if (dimflag[4]) box_change |= BOX_CHANGE_XZ;
  if (dimflag[5]) box_change |= BOX_CHANGE_XY;

  // repeat: no tensile deformation on shrink-wrapped dims
  // b/c shrink wrap will change box-length

  for (int i = 0; i < 3; i++)
    if (set_box.style && (domain->boundary[i][0] >= 2 || domain->boundary[i][1] >= 2))
      error->all(FLERR, "Cannot use fix deform/pressure on a shrink-wrapped boundary");

  // repeat: no tilt deformation on shrink-wrapped 2nd dim
  // b/c shrink wrap will change tilt factor in domain::reset_box()

  if (set[3].style && (domain->boundary[2][0] >= 2 || domain->boundary[2][1] >= 2))
    error->all(FLERR, "Cannot use fix deform/pressure tilt on a shrink-wrapped 2nd dim");
  if (set[4].style && (domain->boundary[2][0] >= 2 || domain->boundary[2][1] >= 2))
    error->all(FLERR, "Cannot use fix deform/pressure tilt on a shrink-wrapped 2nd dim");
  if (set[5].style && (domain->boundary[1][0] >= 2 || domain->boundary[1][1] >= 2))
    error->all(FLERR, "Cannot use fix deform/pressure tilt on a shrink-wrapped 2nd dim");

  // for VOLUME, setup links to other dims
  // fixed, dynamic1, dynamic2

  for (int i = 0; i < 3; i++) {
    if (set[i].style != VOLUME) continue;
    int other1 = (i + 1) % 3;
    int other2 = (i + 2) % 3;

    // Cannot use VOLUME option without at least one deformed dimension
    if (set[other1].style == NONE || set[other1].style == VOLUME)
      if (set[other2].style == NONE || set[other2].style == VOLUME)
        error->all(FLERR, "Fix {} volume setting is invalid", style);

    if (set[other1].style == NONE) {
      set[i].substyle = ONE_FROM_ONE;
      set[i].fixed = other1;
      set[i].dynamic1 = other2;
    } else if (set[other2].style == NONE) {
      set[i].substyle = ONE_FROM_ONE;
      set[i].fixed = other2;
      set[i].dynamic1 = other1;
    } else if (set[other1].style == VOLUME) {
      set[i].substyle = TWO_FROM_ONE;
      set[i].fixed = other1;
      set[i].dynamic1 = other2;
    } else if (set[other2].style == VOLUME) {
      set[i].substyle = TWO_FROM_ONE;
      set[i].fixed = other2;
      set[i].dynamic1 = other1;
    } else {
      set[i].substyle = ONE_FROM_TWO;
      set[i].dynamic1 = other1;
      set[i].dynamic2 = other2;
    }
  }

  // repeat: set varflag

  for (int i = 0; i < 7; i++)
    if (set_extra[i].pvar_flag) varflag = 1;

  // repeat: reneighboring only forced if flips can occur due to shape changes

  if ((!force_reneighbor) && flipflag && (set[3].style || set[4].style || set[5].style)) {
    force_reneighbor = 1;
    irregular = new Irregular(lmp);
  }

  // set initial values at time fix deform/pressure is issued

  set_box.vol_initial = domain->xprd * domain->yprd * domain->zprd;

  // populate coupled pressure controls

  if (pcouple != NOCOUPLE) {

    if (domain->dimension == 2)
      if (pcouple == XYZ || pcouple == XZ || pcouple == YZ)
        error->all(FLERR, "Cannot couple Z dimension in fix deform/pressure in 2D");

    int coupled_indices[3] = {0};
    int j = -1;

    if (pcouple == XYZ || pcouple == XY || pcouple == XZ)
      coupled_indices[0] = 1;
    if (pcouple == XYZ || pcouple == XY || pcouple == YZ)
      coupled_indices[1] = 1;
    if (pcouple == XYZ || pcouple == XZ || pcouple == YZ)
      coupled_indices[2] = 1;

    // Check coupled styles and find reference
    for (int i = 0; i < 3; i++) {
      if (coupled_indices[i]) {
        set_extra[i].coupled_flag = 1;
        if (set[i].style != PRESSURE && set[i].style != NONE)
          error->all(FLERR, "Cannot couple non-pressure-controlled dimensions");
        if (set[i].style == PRESSURE)
          j = i;
      }
    }

    if (j == -1)
      error->all(FLERR, "Must specify deformation style for at least one coupled dimension");

    // Copy or compare data for each coupled dimension

    for (int i = 0; i < 3; i++) {
      if (coupled_indices[i]) {
        // Copy coupling information if dimension style is undefined
        if (set[i].style == NONE) {
          set[i].style = PRESSURE;
          dimflag[i] = 1;
          set_extra[i].pgain = set_extra[j].pgain;
          if (set_extra[j].pvar_flag) {
            set_extra[i].pstr = set_extra[j].pstr;
            set_extra[i].pvar_flag = 1;
          } else {
            set_extra[i].ptarget = set_extra[j].ptarget;
          }
        } else {
          // Check for incompatibilities in style
          if (set[j].style != set[i].style && set[i].style != NONE)
            error->all(FLERR, "Cannot couple dimensions with different control options");
          if (set[j].style != PRESSURE) continue;

          // If pressure controlled, check for incompatibilities in parameters
          if (set_extra[i].pgain != set_extra[j].pgain || set_extra[i].pvar_flag != set_extra[j].pvar_flag ||
              set_extra[i].ptarget != set_extra[j].ptarget)
            error->all(FLERR, "Coupled dimensions must have identical gain parameters");

          if (set_extra[j].pvar_flag)
            if (strcmp(set_extra[i].pstr, set_extra[j].pstr) != 0)
              error->all(FLERR, "Coupled dimensions must have the same target pressure");
        }
      }
    }
  }

  // if vol/balance/p used, must have 2 free dimensions

  if (vol_balance_flag) {
    for (int i = 0; i < 3; i++) {
      if (set[i].style != VOLUME) continue;
      if (set[i].substyle != TWO_FROM_ONE)
        error->all(FLERR, "Two dimensions must maintain constant volume to use the vol/balance/p option");
    }
  }

  // set strain_flag, note that this flag is also set for NONE as
  //   apply_strain() handles its behavior in parent fix deform

  strain_flag = 0;
  for (int i = 0; i < 6; i++)
    if (set[i].style != VOLUME && set[i].style != PRESSURE && set[i].style != PMEAN)
      strain_flag = 1;

  // set pressure_flag

  pressure_flag = 0;
  for (int i = 0; i < 6; i++) {
    if (set[i].style == PRESSURE || set[i].style == PMEAN) {
      pressure_flag = 1;
      if (set_extra[i].pgain <= 0.0)
        error->all(FLERR, "Illegal fix deform/pressure gain constant, must be positive");
    }
    if (set_extra[i].coupled_flag) pressure_flag = 1;
  }
  if (set_box.style == PRESSURE) pressure_flag = 1;
  if (vol_balance_flag) pressure_flag = 1;

  // check conflict between constant volume/pressure

  volume_flag = 0;
  for (int i = 0; i < 3; i++)
    if (set[i].style == VOLUME)
      volume_flag = 1;

  if (volume_flag)
    for (int i = 0; i < 6; i++)
      if (set[i].style == PMEAN)
        error->all(FLERR, "Cannot use fix deform/pressure to assign constant volume and pressure");

  // check conflicts between x,y,z styles and box

  if (set_box.style)
    for (int i = 0; i < 3; i++)
      if (set[i].style == FINAL || set[i].style == DELTA || set[i].style == SCALE || set[i].style == PMEAN || set[i].style == VARIABLE)
        error->all(FLERR, "Cannot use fix deform/pressure box parameter with x, y, or z styles other than vel, erate, trate, pressure, and wiggle");

  // check pressure used for max rate and normalize error flag

  if (!pressure_flag && max_h_rate != 0)
    error->all(FLERR, "Can only assign a maximum strain rate using pressure-controlled dimensions");

  if (!pressure_flag && normalize_pressure_flag)
    error->all(FLERR, "Can only normalize error using pressure-controlled dimensions");

  // Create pressure compute, if needed

  pflag = 0;
  tflag = 0;
  if (pressure_flag) {
    // create a new compute temp style
    // id = fix-ID + temp
    // compute group = all since pressure is always global (group all)
    //   and thus its KE/temperature contribution should use group all

    id_temp = utils::strdup(std::string(id) + "_temp");
    temperature = modify->add_compute(fmt::format("{} all temp", id_temp));
    tflag = 1;

    // create a new compute pressure style
    // id = fix-ID + press, compute group = all
    // pass id_temp as 4th arg to pressure constructor

    id_press = utils::strdup(std::string(id) + "_press");
    pressure = modify->add_compute(fmt::format("{} all pressure {}", id_press, id_temp));
    pflag = 1;
  }
}

/* ---------------------------------------------------------------------- */

FixDeformPressure::~FixDeformPressure()
{
  if (set_extra)
    for (int i = 0; i < 7; i++)
      delete[] set_extra[i].pstr;
  delete[] set_extra;

  delete[] set_box.hstr;
  delete[] set_box.hratestr;

  // delete temperature and pressure if fix created them

  if (tflag) modify->delete_compute(id_temp);
  if (pflag) modify->delete_compute(id_press);
  delete [] id_temp;
  delete [] id_press;
}

/* ---------------------------------------------------------------------- */

void FixDeformPressure::init()
{
  FixDeform::init();

  set_box.vol_start = domain->xprd * domain->yprd * domain->zprd;

  // reset cumulative counters to match resetting "start" variables in parent

  for (int i = 0; i < 7; i++) {
    set_extra[i].cumulative_remap = 0.0;
    set_extra[i].cumulative_shift = 0.0;
    set_extra[i].cumulative_vshift[0] = 0.0;
    set_extra[i].cumulative_vshift[1] = 0.0;
    set_extra[i].cumulative_vshift[2] = 0.0;
  }

  // check optional variables for PRESSURE or PMEAN style

  for (int i = 0; i < 7; i++) {
    if (!set_extra[i].pvar_flag) continue;
    set_extra[i].pvar = input->variable->find(set_extra[i].pstr);
    if (set_extra[i].pvar < 0)
      error->all(FLERR, "Variable name {} for fix deform/pressure does not exist", set_extra[i].pstr);
    if (!input->variable->equalstyle(set_extra[i].pvar))
      error->all(FLERR, "Variable {} for fix deform/pressure is invalid style", set_extra[i].pstr);
  }

  // Find pressure/temp computes if needed

  if (pressure_flag) {
    temperature = modify->get_compute_by_id(id_temp);
    if (!temperature) {
      error->all(FLERR, "Temperature compute ID {} for fix {} does not exist", id_temp, style);
    } else {
      if (temperature->tempflag == 0)
        error->all(FLERR, "Compute ID {} for fix {} does not compute a temperature", id_temp, style);
    }

    pressure = modify->get_compute_by_id(id_press);
    if (!pressure)
      error->all(FLERR,"Pressure compute ID {} for fix {} does not exist", id_press, style);
    if (pressure->pressflag == 0)
      error->all(FLERR,"Compute ID {} for fix {} does not compute pressure", id_press, style);
  }

  // if yz [3] changes and will cause box flip, then xy [5] cannot be changing
  // this is b/c the flips would induce continuous changes in xz
  //   in order to keep the edge vectors of the flipped shape matrix
  //   an integer combination of the edge vectors of the unflipped shape matrix
  // error if style PRESSURE/ERATEER for yz, can't calculate if box flip occurs

  if (set[3].style && set[5].style) {
    if (flipflag && set[3].style == PRESSURE)
      error->all(FLERR, "Fix {} cannot use yz pressure with xy", style);
    if (flipflag && set[3].style == ERATERS)
      error->all(FLERR, "Fix {} cannot use yz erate/rescale with xy", style);
  }
}

/* ----------------------------------------------------------------------
   compute T,P before integrator starts
------------------------------------------------------------------------- */

void FixDeformPressure::setup(int /*vflag*/)
{
  // trigger virial computation, if needed, on next timestep
  if (pressure_flag) pressure->addstep(update->ntimestep + 1);
}

/* ---------------------------------------------------------------------- */

void FixDeformPressure::end_of_step()
{
  // wrap variable evaluations with clear/add

  if (varflag) modify->clearstep_compute();

  // set new box size for strain-based dims

  if (strain_flag) {
    FixDeform::apply_strain();

    for (int i = 3; i < 6; i++) {
      if (set[i].style == ERATERS) {
        double L = domain->zprd;
        if (i == 5) L = domain->yprd;

        h_rate[i] = set[i].rate * L;
        set_extra[i].cumulative_shift += update->dt * h_rate[i];
        set[i].tilt_target = set[i].tilt_start + set_extra[i].cumulative_shift;
      }
    }
  }

  // set new box size for pressure-based dims

  if (pressure_flag) {
    temperature->compute_vector();
    pressure->compute_vector();
    pressure->compute_scalar();
    for (int i = 0; i < 3; i++) {
      if (!set_extra[i].saved) {
        set_extra[i].saved = 1;
        set_extra[i].prior_rate = 0.0;
        set_extra[i].prior_pressure = pressure->vector[i];
      }
    }
    apply_pressure();
  }

  // set new box size for VOLUME dims that are linked to other dims
  // NOTE: still need to set h_rate for these dims

  if (volume_flag) apply_volume();

  // apply any final box scalings

  if (set_box.style) apply_box();

  // Save pressure/strain rate if required

  if (pressure_flag) {
    for (int i = 0; i < 3; i++) {
      set_extra[i].prior_pressure = pressure->vector[i];
      set_extra[i].prior_rate = ((set[i].hi_target - set[i].lo_target) /
                           domain->prd[i] - 1.0)  / update->dt;
    }
  }

  if (varflag) modify->addstep_compute(update->ntimestep + nevery);

  // If tilting while evolving linear dimension, sum remapping effects
  // otherwise, update_domain() will inaccurately use the current
  // linear dimension to apply prior remappings

  for (int i = 3; i < 6; i++) {
    int idenom = 0;
    if (i == 3) idenom = 1;
    if (set[i].style && (set_box.style || set[idenom].style) && domain->periodicity[idenom]) {
      // Add prior remappings. If the box remaps this timestep, don't
      // add it yet so update_domain() will first detect the remapping
      set[i].tilt_target += set_extra[i].cumulative_remap;

      // Update remapping for next timestep
      double prd = set[idenom].hi_target - set[idenom].lo_target;
      double prdinv = 1.0 / prd;
      if (set[i].tilt_target * prdinv < -0.5)
        set_extra[i].cumulative_remap += prd;
      if (set[i].tilt_target * prdinv > 0.5)
        set_extra[i].cumulative_remap -= prd;
    }
  }

  FixDeform::update_domain();

  // trigger virial computation, if needed, on next timestep

  if (pressure_flag)
    pressure->addstep(update->ntimestep+1);
}

/* ----------------------------------------------------------------------
   apply pressure controls
------------------------------------------------------------------------- */

void FixDeformPressure::apply_pressure()
{
  // If variable pressure, calculate current target
  for (int i = 0; i < 6; i++)
    if (set[i].style == PRESSURE || set[i].style == PMEAN)
      if (set_extra[i].pvar_flag)
        set_extra[i].ptarget = input->variable->compute_equal(set_extra[i].pvar);

  // Find current (possibly coupled/hydrostatic) pressure for X, Y, Z
  double *tensor = pressure->vector;
  double scalar = pressure->scalar;
  double p_current[3] = {0.0, 0.0, 0.0};

  if (pcouple == XYZ) {
    double ave = THIRD * (tensor[0] + tensor[1] + tensor[2]);
    p_current[0] = p_current[1] = p_current[2] = ave;
  } else if (pcouple == XY) {
    double ave = 0.5 * (tensor[0] + tensor[1]);
    p_current[0] = p_current[1] = ave;
    p_current[2] = tensor[2];
  } else if (pcouple == YZ) {
    double ave = 0.5 * (tensor[1] + tensor[2]);
    p_current[1] = p_current[2] = ave;
    p_current[0] = tensor[0];
  } else if (pcouple == XZ) {
    double ave = 0.5 * (tensor[0] + tensor[2]);
    p_current[0] = p_current[2] = ave;
    p_current[1] = tensor[1];
  } else {
    if (set[0].style == PRESSURE) p_current[0] = tensor[0];
    else if (set[0].style == PMEAN) p_current[0] = scalar;

    if (set[1].style == PRESSURE) p_current[1] = tensor[1];
    else if (set[1].style == PMEAN) p_current[1] = scalar;

    if (set[2].style == PRESSURE) p_current[2] = tensor[2];
    else if (set[2].style == PMEAN) p_current[2] = scalar;
  }

  for (int i = 0; i < 3; i++) {
    if (set[i].style != PRESSURE && set[i].style != PMEAN) continue;

    h_rate[i] = set_extra[i].pgain * (p_current[i] - set_extra[i].ptarget);

    if (normalize_pressure_flag) {
      if (set_extra[i].ptarget == 0) {
        if (max_h_rate == 0) {
          error->all(FLERR, "Cannot normalize error for zero pressure without defining a max rate");
        } else h_rate[i] = max_h_rate * h_rate[i] / fabs(h_rate[i]);
      } else h_rate[i] /= fabs(set_extra[i].ptarget);
    }

    if (max_h_rate != 0)
      if (fabs(h_rate[i]) > max_h_rate)
        h_rate[i] = max_h_rate * h_rate[i] / fabs(h_rate[i]);

    h_ratelo[i] = -0.5 * h_rate[i];

    double shift = domain->prd[i] * update->dt * h_rate[i];
    set_extra[i].cumulative_shift += shift;
    set[i].lo_target = set[i].lo_start - 0.5 * set_extra[i].cumulative_shift;
    set[i].hi_target = set[i].hi_start + 0.5 * set_extra[i].cumulative_shift;
  }

  for (int i = 3; i < 6; i++) {
    if (set[i].style != PRESSURE) continue;

    double L, pcurrent;
    if (i == 3) {
      L = domain->zprd;
      pcurrent = tensor[5];
    } else if (i == 4) {
      L = domain->zprd;
      pcurrent = tensor[4];
    } else {
      L = domain->yprd;
      pcurrent = tensor[3];
    }

    h_rate[i] = L * set_extra[i].pgain * (pcurrent - set_extra[i].ptarget);
    if (normalize_pressure_flag) {
      if (set_extra[i].ptarget == 0) {
        if (max_h_rate == 0) {
          error->all(FLERR, "Cannot normalize error for zero pressure without defining a max rate");
        } else h_rate[i] = max_h_rate * h_rate[i] / fabs(h_rate[i]);
      } else h_rate[i] /= fabs(set_extra[i].ptarget);
    }

    if (max_h_rate != 0)
      if (fabs(h_rate[i]) > max_h_rate)
        h_rate[i] = max_h_rate * h_rate[i] / fabs(h_rate[i]);

    set_extra[i].cumulative_shift += update->dt * h_rate[i];
    set[i].tilt_target = set[i].tilt_start + set_extra[i].cumulative_shift;
  }
}

/* ----------------------------------------------------------------------
   apply volume controls
------------------------------------------------------------------------- */

void FixDeformPressure::apply_volume()
{
  double e1, e2;
  int linked_pressure = 0;

  for (int i = 0; i < 3; i++) {
    if (set[i].style != VOLUME) continue;

    int dynamic1 = set[i].dynamic1;
    int dynamic2 = set[i].dynamic2;
    int fixed = set[i].fixed;
    double v0 = set[i].vol_start;
    double shift = 0.0;

    if (set[i].substyle == ONE_FROM_ONE) {
      shift = 0.5 * (v0 / (set[dynamic1].hi_target - set[dynamic1].lo_target) /
             (set[fixed].hi_start-set[fixed].lo_start));
    } else if (set[i].substyle == ONE_FROM_TWO) {
      shift = 0.5 * (v0 / (set[dynamic1].hi_target - set[dynamic1].lo_target) /
             (set[dynamic2].hi_target - set[dynamic2].lo_target));
    } else if (set[i].substyle == TWO_FROM_ONE) {
      if (!vol_balance_flag) {
        shift = 0.5 * sqrt(v0 * (set[i].hi_start - set[i].lo_start) /
                   (set[dynamic1].hi_target - set[dynamic1].lo_target) /
                   (set[fixed].hi_start - set[fixed].lo_start));
      } else {
        double dt = update->dt;
        double e1i = set_extra[i].prior_rate;
        double e2i = set_extra[fixed].prior_rate;
        double L1i = domain->prd[i];
        double L2i = domain->prd[fixed];
        double L3i = domain->prd[dynamic1];
        double L3 = (set[dynamic1].hi_target - set[dynamic1].lo_target);
        double Vi = L1i * L2i * L3i;
        double V = L3 * L1i * L2i;
        double e3 = (L3 / L3i - 1.0) / dt;
        double p1 = pressure->vector[i];
        double p2 = pressure->vector[fixed];
        double p1i = set_extra[i].prior_pressure;
        double p2i = set_extra[fixed].prior_pressure;
        double denominator;

        if (e3 == 0) {
          e1 = 0.0;
          e2 = 0.0;
          shift = 0.5 * L1i;
        } else if (e1i == 0 || e2i == 0 || (p2 == p2i && p1 == p1i)) {
          // If no prior strain or no change in pressure (initial step) just scale shift by relative box lengths
          shift = 0.5 * sqrt(v0 * L1i / L3 / L2i);
        } else {
          if (!linked_pressure) {
            // Calculate first strain rate by expanding stress to linear order, p1(t+dt) = p2(t+dt)
            // Calculate second strain rate to preserve volume
            denominator = p2 - p2i + e2i * ((p1 - p1i) / e1i);
            if (denominator != 0.0 && e1i != 0.0) {
              e1 = (((p2 - p2i) * (Vi - V) / (V * dt)) - e2i * (p1 - p2)) / denominator;
            } else {
              e1 = e2i;
            }
            e2 = (Vi - V * (1 + e1 * dt)) / (V * (1 + e1 * dt) * dt);

            // If strain rate exceeds limit in either dimension, cap it at the maximum compatible rate
            if (max_h_rate != 0) {
              if ((fabs(e1) > max_h_rate) || (fabs(e2) > max_h_rate)) {
                if (fabs(e1) > fabs(e2))
                  adjust_linked_rates(e1, e2, e3, Vi, V);
                else
                  adjust_linked_rates(e2, e1, e3, Vi, V);
              }
            }
            shift = 0.5 * L1i * (1.0 + e1 * dt);
            linked_pressure = 1;
          } else {
            // Already calculated value of e2
            shift = 0.5 * L1i * (1.0 + e2 * dt);
          }
        }
      }
    }

    h_rate[i] = (2.0 * shift / domain->prd[i] - 1.0) / update->dt;
    h_ratelo[i] = -0.5 * h_rate[i];

    set[i].lo_target = 0.5 * (set[i].lo_start + set[i].hi_start) - shift;
    set[i].hi_target = 0.5 * (set[i].lo_start + set[i].hi_start) + shift;
  }
}


/* ----------------------------------------------------------------------
   Rescale volume preserving strain rates to enforce max rate
------------------------------------------------------------------------- */

void FixDeformPressure::adjust_linked_rates(double &e_larger, double &e_smaller, double e3, double Vi, double V)
{
  double dt = update->dt;
  double e_lim_positive = (Vi - V * (1 + max_h_rate * dt)) / (V * (1 + max_h_rate * dt) * dt);
  double e_lim_negative = (Vi - V * (1 - max_h_rate * dt)) / (V * (1 - max_h_rate * dt) * dt);
  if ((e_larger * e3) >= 0) {
    if (e_larger > 0.0) {
      // Same sign as primary strain rate, cap third dimension
      e_smaller = -max_h_rate;
      e_larger = e_lim_negative;
    } else {
      e_smaller = max_h_rate;
      e_larger = e_lim_positive;
    }
  } else {
    // Opposite sign, set to maxrate.
    if (e_larger > 0.0) {
      e_larger = max_h_rate;
      e_smaller = e_lim_positive;
    } else {
      e_larger = -max_h_rate;
      e_smaller = e_lim_negative;
    }
  }
}

/* ----------------------------------------------------------------------
   apply box controls
------------------------------------------------------------------------- */

void FixDeformPressure::apply_box()
{
  int i;
  double scale, shift = 0.0;
  double v_rate;

  if (set_box.style == VOLUME) {
    double v0 = set_box.vol_start;
    double v = 1.0;
    for (i = 0; i < 3; i++)
      v *= (set[i].hi_target - set[i].lo_target);

    scale = std::pow(v0 / v, THIRD);
    for (i = 0; i < 3; i++) {
      shift = 0.5 * (set[i].hi_target - set[i].lo_target) * scale;
      set[i].lo_target = 0.5 * (set[i].lo_start + set[i].hi_start) - shift;
      set[i].hi_target = 0.5 * (set[i].lo_start + set[i].hi_start) + shift;

      // Recalculate h_rate
      h_rate[i] = (set[i].hi_target - set[i].lo_target) / domain->prd[i] - 1.0;
      h_rate[i] /= update->dt;
      h_ratelo[i] = -0.5 * h_rate[i];
    }

  } else if (set_box.style == PRESSURE) {

    // If variable pressure, calculate current target
    if (set_extra[6].pvar_flag)
      set_extra[6].ptarget = input->variable->compute_equal(set_extra[6].pvar);

    v_rate = set_extra[6].pgain * (pressure->scalar - set_extra[6].ptarget);

    if (normalize_pressure_flag) {
      if (set_extra[6].ptarget == 0) {
        if (max_h_rate == 0) {
          error->all(FLERR, "Cannot normalize error for zero pressure without defining a max rate");
        } else v_rate = max_h_rate * v_rate / fabs(v_rate);
      } else v_rate /= fabs(set_extra[6].ptarget);
    }

    if (max_h_rate != 0)
      if (fabs(v_rate) > max_h_rate)
        v_rate = max_h_rate * v_rate / fabs(v_rate);

    for (i = 0; i < 3; i++) {
      shift = (set[i].hi_target - set[i].lo_target) * update->dt * v_rate;
      set_extra[6].cumulative_vshift[i] += shift;

      if (set[i].style == NONE) {
        // Overwrite default targets of current length
        set[i].lo_target = set[i].lo_start;
        set[i].hi_target = set[i].hi_start;
      }

      set[i].lo_target -= 0.5 * set_extra[6].cumulative_vshift[i];
      set[i].hi_target += 0.5 * set_extra[6].cumulative_vshift[i];

      // Recalculate h_rate
      h_rate[i] = (set[i].hi_target - set[i].lo_target) / domain->prd[i] - 1.0;
      h_rate[i] /= update->dt;
      h_ratelo[i] = -0.5 * h_rate[i];
    }
  }
}

/* ----------------------------------------------------------------------
   write Set data to restart file
------------------------------------------------------------------------- */

void FixDeformPressure::write_restart(FILE *fp)
{
  if (comm->me == 0) {
    int size = 7 * sizeof(Set) + 7 * sizeof(SetExtra);
    fwrite(&size, sizeof(int), 1, fp);
    fwrite(set, sizeof(Set), 6, fp);
    fwrite(&set_box, sizeof(Set), 1, fp);
    fwrite(set_extra, sizeof(SetExtra), 7, fp);
  }
}

/* ----------------------------------------------------------------------
   use selected state info from restart file to restart the Fix
------------------------------------------------------------------------- */

void FixDeformPressure::restart(char *buf)
{
  int n = 0;
  int samestyle = 1;
  Set *set_restart = (Set *) buf;
  for (int i = 0; i < 6; ++i) {
    // restore data from initial state
    set[i].lo_initial = set_restart[i].lo_initial;
    set[i].hi_initial = set_restart[i].hi_initial;
    set[i].vol_initial = set_restart[i].vol_initial;
    set[i].tilt_initial = set_restart[i].tilt_initial;

    // check if style settings are consistent
    if (set[i].style != set_restart[i].style)
      samestyle = 0;
    if (set[i].substyle != set_restart[i].substyle)
      samestyle = 0;
  }
  n += 6 * sizeof(Set);

  // Only restore relevant box variables & check consistency
  Set set_box_restart;
  memcpy(&set_box_restart, (Set *) &buf[n], sizeof(Set));
  set_box.vol_initial = set_box_restart.vol_initial;
  if (set_box.style != set_box_restart.style)
    samestyle = 0;

  if (!samestyle)
    error->all(FLERR, "Fix deform/pressure settings not consistent with restart");

  n += sizeof(Set);
  auto *set_extra_restart = (SetExtra *) &buf[n];
  for (int i = 0; i < 7; ++i) {
    set_extra[i].saved = set_extra_restart[i].saved;
    set_extra[i].prior_rate = set_extra_restart[i].prior_rate;
    set_extra[i].prior_pressure = set_extra_restart[i].prior_pressure;
  }
}

/* ---------------------------------------------------------------------- */

void FixDeformPressure::options(int i, int narg, char **arg)
{
  pcouple = NOCOUPLE;
  max_h_rate = 0.0;
  vol_balance_flag = 0;
  normalize_pressure_flag = 0;

  // parse only options not handled by parent class

  int iarg;
  while (i < (int) leftover_iarg.size()) {
    iarg = leftover_iarg[i];
    if (strcmp(arg[iarg], "couple") == 0) {
      if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure couple", error);
      if (strcmp(arg[iarg + 1], "xyz") == 0) pcouple = XYZ;
      else if (strcmp(arg[iarg + 1], "xy") == 0) pcouple = XY;
      else if (strcmp(arg[iarg + 1], "yz") == 0) pcouple = YZ;
      else if (strcmp(arg[iarg + 1], "xz") == 0) pcouple = XZ;
      else if (strcmp(arg[iarg + 1], "none") == 0) pcouple = NOCOUPLE;
      else error->all(FLERR, "Illegal fix deform/pressure couple command: {}", arg[iarg + 1]);
      i += 2;
    } else if (strcmp(arg[iarg], "max/rate") == 0) {
      if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure max/rate", error);
      max_h_rate = utils::numeric(FLERR, arg[iarg + 1], false, lmp);
      if (max_h_rate <= 0.0)
        error->all(FLERR, "Maximum strain rate must be a positive, non-zero value");
      i += 2;
    } else if (strcmp(arg[iarg], "normalize/pressure") == 0) {
      if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure normalize/pressure", error);
      normalize_pressure_flag = utils::logical(FLERR, arg[iarg + 1], false, lmp);
      i += 2;
    } else if (strcmp(arg[iarg], "vol/balance/p") == 0) {
      if (iarg + 2 > narg) utils::missing_cmd_args(FLERR, "fix deform/pressure vol/balance/p", error);
      vol_balance_flag = utils::logical(FLERR, arg[iarg + 1], false, lmp);
      i += 2;
    } else error->all(FLERR, "Illegal fix deform/pressure command: {}", arg[iarg]);
  }
}

/* ---------------------------------------------------------------------- */

int FixDeformPressure::modify_param(int narg, char **arg)
{
  if (strcmp(arg[0], "temp") == 0) {
    if (narg < 2) error->all(FLERR, "Illegal fix_modify command");
    if (tflag) {
      modify->delete_compute(id_temp);
      tflag = 0;
    }
    delete[] id_temp;
    id_temp = utils::strdup(arg[1]);

    temperature = modify->get_compute_by_id(arg[1]);
    if (!temperature)
      error->all(FLERR, "Could not find fix_modify temperature compute ID: ", arg[1]);

    if (temperature->tempflag == 0)
      error->all(FLERR, "Fix_modify temperature compute {} does not compute temperature", arg[1]);
    if (temperature->igroup != 0 && comm->me == 0)
      error->warning(FLERR, "Temperature compute {} for fix {} is not for group all: {}",
                     arg[1], style, group->names[temperature->igroup]);

    // reset id_temp of pressure to new temperature ID

    auto *icompute = modify->get_compute_by_id(id_press);
    if (!icompute)
      error->all(FLERR, "Pressure compute ID {} for fix {} does not exist", id_press, style);
    icompute->reset_extra_compute_fix(id_temp);

    return 2;

  } else if (strcmp(arg[0], "press") == 0) {
    if (narg < 2) error->all(FLERR, "Illegal fix_modify command");
    if (pflag) {
      modify->delete_compute(id_press);
      pflag = 0;
    }
    delete[] id_press;
    id_press = utils::strdup(arg[1]);

    pressure = modify->get_compute_by_id(arg[1]);
    if (!pressure) error->all(FLERR, "Could not find fix_modify pressure compute ID: {}", arg[1]);
    if (pressure->pressflag == 0)
      error->all(FLERR, "Fix_modify pressure compute {} does not compute pressure", arg[1]);
    return 2;
  }

  return 0;
}
