Skip to content

Instantly share code, notes, and snippets.

@krassowski
Created June 29, 2017 13:46
Show Gist options
  • Save krassowski/64abe62ea8172c9ec4f8ebde78c0635d to your computer and use it in GitHub Desktop.
Save krassowski/64abe62ea8172c9ec4f8ebde78c0635d to your computer and use it in GitHub Desktop.
ForceFields
#ifndef FORCEFIELD_HPP
#define FORCEFIELD_HPP
#include "vector.hpp"
#include "atom.hpp"
#include "system.hpp"
// I moved all code to one file for gist easier sharing ;)
// change to potential energy??
class ForceField {
public:
// force after "interaction"
virtual Vector3d calc_force(Atom* atom) = 0;
// potential energy
virtual double calc_u(Atom* atom) = 0;
// representation of force_field if any
virtual string get_representation();
string const name;
ForceField(string name):
name(name) {}
};
string ForceField::get_representation() {
return "{}";
}
class SoftWall : public ForceField
{
private:
Hyperplane<double, 3> *plane;
double width;
double softness; // hardness essentially(!)
public:
SoftWall(Vector3d normal, Vector3d position):
SoftWall(normal, position, 1, 1) {}
SoftWall(Vector3d normal, double distance):
SoftWall(normal, distance, 1, 1) {}
// position: a position of a point which should belong to the wall
SoftWall(Vector3d normal, Vector3d position, double width, double softness):
ForceField("soft_wall"), width(width), softness(softness)
{
// "Construct a plane from its normal n and a point e onto the plane."
plane = new Hyperplane<double, 3>(normal, position);
}
// distance: distance from origin centre
SoftWall(Vector3d normal, double distance, double width, double softness):
ForceField("soft_wall"), width(width), softness(softness)
{
// "Constructs a plane from its normal n and distance to the origin d
// such that the algebraic equation of the plane is $n \cdot x + d = 0$."
plane = new Hyperplane<double, 3>(normal, distance);
}
double calc_u(Atom* atom) override;
Vector3d calc_force(Atom* atom) override;
string get_representation() override;
};
double SoftWall::calc_u(Atom* atom)
{
double distance = plane->absDistance(atom->position);
if (distance <= width)
{
// 1/2 * (w-sqrt(x^2+y^2+z^2))^2*s
return 0.5 * pow(width - distance, 2) * softness;
}
return 0;
};
#include "format.hpp"
string SoftWall::get_representation()
{
using namespace fmt;
auto normal = this->plane->normal();
return format(
"{{"
"\"type\": \"wall\","
" \"width\": {width},"
" \"x\": {x},"
" \"y\": {y},"
" \"z\": {z},"
" \"distance\": {distance}"
"}}",
arg("width", this->width),
arg("x", normal.x()),
arg("y", normal.y()),
arg("z", normal.z()),
arg("distance", this->plane->offset())
);
}
Vector3d SoftWall::calc_force(Atom* atom)
{
Vector3d closest_on_plane = plane->projection(atom->position);
Vector3d projection = closest_on_plane - atom->position;
double distance = projection.norm();
// assert distance == plane->absDistance(atom.position);
if (distance <= width)
{
// return - (2 * projection * softness);
// derivative: $\vec{Force}(\vec{r}) = - \nabla \text{Potential energy}_{\vec{r}} $
// derivative of: 1/2 * (w-sqrt(x^2+y^2+z^2))^2*s
// sx(√x2+z2+y2−w)/√x2+z2+y2
return softness * projection * (distance - width) / distance;
}
return Zero3d;
};
/*
* Simplified Lennard-Jones potential,
* @see AB form on https://en.wikipedia.org/wiki/Lennard-Jones_potential#AB_form
* @see https://chem.libretexts.org/Core/Physical_and_Theoretical_Chemistry/Physical_Properties_of_Matter/Atomic_and_Molecular_Properties/Intermolecular_Forces/Specific_Interactions/Lennard-Jones_Potential
* DOES NOT PLAY WELL
*/
class VanDerWaals: public ForceField
{
private:
System* system;
public:
VanDerWaals(System* system):
ForceField("van_der_waals"), system(system) {}
double calc_u(Atom* atom) override;
Vector3d calc_force(Atom* atom) override;
};
double VanDerWaals::calc_u(Atom* atom)
{
double energy = 0;
for(Atom* other_atom : this->system->atoms)
{
if(other_atom == atom)
continue;
// how close two atom can get to each other (A)
double sigma = atom->radius() + other_atom->radius();
Vector3d difference = atom->position - other_atom->position;
double distance = difference.norm();
// cut-off; sometimes 12 * sigma. Should be somehow smoothed.
if(distance > 6 * sigma)
continue;
// how strongly two atoms attract each other (kj/mol)
double epsilon = 1;
// (Pauli) repulsion
double A = pow(sigma / distance, 12);
// (London dispersion) attraction
double B = pow(sigma / distance, 6);
energy += 4 * epsilon * (A - B);
}
return energy;
}
Vector3d VanDerWaals::calc_force(Atom* atom)
{
/* Force equals to $F = - \nabla U(r)$
* Formula for U is given and explained in calc_u. Partial derivative with respect to x equals:
* $\frac{\partial \:}{\partial \:x}(\frac{A}{(\sqrt{x^2+y^2+z^2})^{12}}) = \frac{12Ax}{(x^2+y^2+z^2)^7}$
* for the attractive component and
* $\frac{\partial \:}{\partial \:x}(\frac{B}{(\sqrt{x^2+y^2+z^2})^{7}}) = \frac{7Bx}{\sqrt{(x^2+y^2+z^2)}^{9}$
*/
Vector3d force = Zero3d;
for(Atom* other_atom : this->system->atoms)
{
if(other_atom == atom)
continue;
// how close two atom can get to each other (A)
double sigma = atom->radius() + other_atom->radius();
Vector3d difference = atom->position - other_atom->position;
double distance = difference.norm();
// cut-off; sometimes 12 * sigma. Should be somehow smoothed.
if(distance > 6 * sigma)
continue;
// how strongly two atoms attract each other (kj/mol)
double epsilon = 1;
force += difference * 48 * epsilon / pow(sigma, 2) * (pow(sigma / distance, 14) - 0.5 * pow(sigma / distance, 8));
}
return force;
}
class Coulomb: public ForceField
{
private:
System* system;
public:
Coulomb(System* system):
ForceField("coulomb"), system(system) {}
double calc_u(Atom* atom) override;
Vector3d calc_force(Atom* atom) override;
};
double Coulomb::calc_u(Atom* atom)
{
double energy = 0;
for(Atom* other_atom : this->system->atoms)
{
if(other_atom == atom)
continue;
Vector3d difference = atom->position - other_atom->position;
double distance = difference.norm();
energy += atom->charge() * other_atom->charge() / distance;
}
return energy;
}
Vector3d Coulomb::calc_force(Atom *atom)
{
/*
* To check that sum of partial derivatives:
* $\frac{q_1 q_2 x}{\sqrt{x^2 + y^2 +z^2}^3} + \partial_y + partial_z$
* equals force from Coulumb Law:
* raise each fraction (each partial) to power of two:
* $\frac{q_1^2 q_2^2 x^2}{\sqrt{x^2 + y^2 +z^2}^6} + \partial_y^2 + partial_z^2$
* add the fractions, move $q_1 q_2$ ahead of parens:
* $\frac{(q_1^2 q_2^2)(x^2 + y^2 + z^2)}{\sqrt{x^2 + y^2 +z^2}^6}$
* change sum of squares to square root:
* $\frac{(q_1 q_2)^2\sqrt{x^2 + y^2 + z^2}^2}{\sqrt{x^2 + y^2 +z^2}^6}$
* reduce:
* $\frac{(q_1 q_2)^2}{\sqrt{x^2 + y^2 +z^2}^4}$
* and again simplify too:
* $\frac{q_1 q_2}{\sqrt{x^2 + y^2 +z^2}^2}$
* which is exactly formulation of Coulumb's Law
*/
Vector3d force = Zero3d;
for(Atom* other_atom : this->system->atoms)
{
if(other_atom == atom)
continue;
Vector3d difference = atom->position - other_atom->position;
double distance = difference.norm();
double qq = atom->charge() * other_atom->charge();
force += atom->position * qq / pow(distance, 3);
}
return force;
}
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment