Skip to content

Potential

Raul edited this page Mar 10, 2021 · 10 revisions

Potentials provide Transversers[1] to PairForces[2] in order to compute forces, energies and/or both at the same time.
Many aspects of the Potential and Transverser interfaces are optional and provide default behavior, when a function is optional it will be denoted as such in this page.

The Potential interface

The Potential interface requires a given class/struct to provide the following public member functions:

Required members:

  • real getCutOff();
    • The maximum cut-off the potential requires.

Optional members (at least one of the following must exist):

  • ForceTransverser getForceTransverser(Box box, shared_ptr<ParticleData> pd);
    • Provides a Transverser that computes the force
    • If not present the force is computed using getForceEnergyTransverser.
  • EnergyTransverser getEnergyTransverser(Box box, shared_ptr<ParticleData> pd);
    • Provides a Transverser that computes the energy
    • If not present the energy is computed using getForceEnergyTransverser.
  • ForceEnergyTransverser getForceEnergyTransverser(Box box, shared_ptr<ParticleData> pd);
    • Provides a Transverser that computes the force and energy at the same time
    • If not present defaults to sequentially computing force and energy one after the other.
  • ComputeTransverser getComputeTransverser(Box box, shared_ptr<ParticleData> pd);
    • Provides a Transverser that performs an arbitrary computation, will be called when PairForces::compute() is called.
    • If not present no work will be performed.

If there is no way to compute either force or energy it will be assumed to be zero. For example energy will be assumed to be zero if the only method defined is getForceTransverser.

A struct/class adhering to the Potential interface can also be ParameterUpdatable[3].
The type(s) returned by these functions must adhere to the Transverser interface described in [1].

Example

//A simple LJ Potential, can compute force, energy or both at the same time.
struct SimpleLJ{
  real rc = 2.5;
  //A host function returning the maximum required cut off for the interaction
  real getCutOff(){
    return rc;
  }
  //A Transverser for computing both energy and force, this same Transverser can be used to compute either force, energy or both at the same time. It is the simplest form of Transverser as it only provides the "compute" and "set" functions
  //When constructed, if the i_force or i_energy pointers are null that computation will be avoided.
  //Notice that it is not required that this struct is defined inside the Potential, it is only required that the functions get*Transverser provide it.
  struct ForceEnergy{
    real4 *force;
    real* energy;
    Box box;
    real rc;
    ForceEnergy(Box i_box, real i_rc, real4* i_force, real* i_energy):
      box(i_box),
      rc(i_rc),
      force(i_force),
      energy(i_energy){
      //All members will be available in the device functions
    }
    //For each pair computes and returns the LJ force and/or energy based only on the positions
    __device__ real4 compute(real4 pi, real4 pj){
      const real3 rij = box.apply_pbc(make_real3(pj)-make_real3(pi));
      const real r2 = dot(rij, rij);
      if(r2>0 and r2< rc*rc){
	return make_real4(force?(lj_force(r2)*rij):real3(),energy?lj_energy(r2):0);
      }
      return real4();
    }
    //There is no "accumulate" function so, for each particle, the result of compute for every neighbour will be summed
    //There is no "zero" function so the total result starts being real4() (or {0,0,0,0}).
    //The "set" function will be called with the accumulation of the result of "compute" for all neighbours. 
    __device__ void set(int id, real4 total){
      //Write the total result to memory if the pointer was provided
      if(force)  force[id] += make_real4(total.x, total.y, total.z, 0);
      if(energy) energy[id] += total.w;
    }
  };

  //Return an instance of the Transverser that will compute only the force (because the energy pointer is null)
  ForceEnergy getForceTransverser(Box box, std::shared_ptr<ParticleData> pd){
    auto force = pd->getForce(access::location::gpu, access::mode::readwrite).raw();    
    return ForceEnergy(box, rc, force, nullptr);
  }

  //These two functions can be ommited if one is not interested in the energy.
  //They provide instances of the Transverser that compute either the energy or the force and energy.
  //Notice that it is not required that the return type is the same in all three cases. Different Transversers can be used in each case.
  ForceEnergy getEnergyTransverser(Box box, std::shared_ptr<ParticleData> pd){
    auto energy = pd->getEnergy(access::location::gpu, access::mode::readwrite).raw();   
    return ForceEnergy(box, rc, nullptr, energy);
  }
  
  ForceEnergy getForceEnergyTransverser(Box box, std::shared_ptr<ParticleData> pd){
    auto force = pd->getForce(access::location::gpu, access::mode::readwrite).raw();
    auto energy = pd->getEnergy(access::location::gpu, access::mode::readwrite).raw();
    return ForceEnergy(box, rc, force, energy);
  }
  
};

This Potential can be used with PairForces as follows:

shared_ptr<Interactor> createPairForces(shared_ptr<ParticleData> pd, shared_ptr<System> sys){
  using PF = PairForces<SimpleLJ>;
  typename PF::Parameters par;
  par.box = Box(boxSize);
  auto pot = std::make_shared<SimpleLJ>();
  return std::make_shared<PF>(pd, sys, par, pot);  
}

See examples/customPotentials.cu for additional examples.

See RadialPotential[4] for an already implemented Potential available in UAMMD.

References

[1] https://github.com/RaulPPelaez/UAMMD/wiki/Transverser
[2] https://github.com/RaulPPelaez/UAMMD/wiki/PairForces
[3] https://github.com/RaulPPelaez/UAMMD/wiki/ParameterUpdatable
[4] https://github.com/RaulPPelaez/UAMMD/wiki/RadialPotential









Clone this wiki locally