Commit 4be5daf1 authored by peastman's avatar peastman
Browse files

Created LocalCoordinatesSite, including reference implementation

parent ede48b85
......@@ -9,7 +9,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2012 Stanford University and the Authors. *
* Portions copyright (c) 2012-2014 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
......@@ -32,6 +32,7 @@
* USE OR OTHER DEALINGS IN THE SOFTWARE. *
* -------------------------------------------------------------------------- */
#include <openmm/Vec3.h>
#include "internal/windowsExport.h"
#include <vector>
......@@ -166,6 +167,65 @@ private:
double weight12, weight13, weightCross;
};
/**
* This is a VirtualSite that uses the locations of three other particles to compute a local
* coordinate system, then places the virtual site at a fixed location in that coordinate
* system. The origin of the coordinate system and the directions of its x and y axes
* are each specified as a weighted sum of the locations of the three particles:
*
* origin = w<sup>o</sup><sub>1</sub>r<sub>1</sub> + w<sup>o</sup><sub>2</sub>r<sub>2</sub> + w<sup>o</sup><sub>3</sub>r<sub>3</sub>
*
* xdir = w<sup>x</sup><sub>1</sub>r<sub>1</sub> + w<sup>x</sup><sub>2</sub>r<sub>2</sub> + w<sup>x</sup><sub>3</sub>r<sub>3</sub>
*
* ydir = w<sup>y</sup><sub>1</sub>r<sub>1</sub> + w<sup>y</sup><sub>2</sub>r<sub>2</sub> + w<sup>y</sup><sub>3</sub>r<sub>3</sub>
*
* For the origin, the three weights must add to one. For example if
* (w<sup>o</sup><sub>1</sub>, w<sup>o</sup><sub>2</sub>, w<sup>o</sup><sub>3</sub>) = (1.0, 0.0, 0.0),
* the origin of the local coordinate system is at the location of particle 1. For xdir and ydir,
* the weights must add to zero. For excample, if
* (w<sup>x</sup><sub>1</sub>, w<sup>x</sup><sub>2</sub>, w<sup>x</sup><sub>3</sub>) = (-1.0, 0.5, 0.5),
* the x axis points from particle 1 toward the midpoint between particles 2 and 3.
*
* The z direction is computed as zdir = xdir x ydir. To ensure the axes are all orthogonal,
* ydir is then recomputed as ydir = zdir x xdir. All three axis vectors are then normalize, and
* the virtual site location is set to
*
* origin + x*xdir + y*ydir + z*zdir
*/
class OPENMM_EXPORT LocalCoordinatesSite : public VirtualSite {
public:
/**
* Create a new LocalCoordinatesSite virtual site.
*
* @param particle1 the index of the first particle
* @param particle2 the index of the second particle
* @param particle3 the index of the third particle
* @param originWeights the weight factors for the three particles when computing the origin location
* @param xWeights the weight factors for the three particles when computing xdir
* @param yWeights the weight factors for the three particles when computing ydir
* @param localPosition the position of the virtual site in the local coordinate system
*/
LocalCoordinatesSite(int particle1, int particle2, int particle3, const Vec3& originWeights, const Vec3& xWeights, const Vec3& yWeights, const Vec3& localPosition);
/**
* Get the weight factors for the three particles when computing the origin location.
*/
const Vec3& getOriginWeights() const;
/**
* Get the weight factors for the three particles when computing xdir.
*/
const Vec3& getXWeights() const;
/**
* Get the weight factors for the three particles when computing ydir.
*/
const Vec3& getYWeights() const;
/**
* Get the position of the virtual site in the local coordinate system.
*/
const Vec3& getLocalPosition() const;
private:
Vec3 originWeights, xWeights, yWeights, localPosition;
};
} // namespace OpenMM
#endif /*OPENMM_VIRTUALSITE_H_*/
......@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2012 Stanford University and the Authors. *
* Portions copyright (c) 2012-2014 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
......@@ -31,6 +31,7 @@
#include "openmm/VirtualSite.h"
#include "openmm/OpenMMException.h"
#include <cmath>
#include <vector>
using namespace OpenMM;
......@@ -103,3 +104,34 @@ double OutOfPlaneSite::getWeight13() const {
double OutOfPlaneSite::getWeightCross() const {
return weightCross;
}
LocalCoordinatesSite::LocalCoordinatesSite(int particle1, int particle2, int particle3, const Vec3& originWeights, const Vec3& xWeights, const Vec3& yWeights, const Vec3& localPosition) :
originWeights(originWeights), xWeights(xWeights), yWeights(yWeights), localPosition(localPosition) {
if (fabs(originWeights[0]+originWeights[1]+originWeights[2]-1.0) > 1e-6)
throw OpenMMException("LocalCoordinatesSite: Weights for computing origin must add to 1");
if (fabs(xWeights[0]+xWeights[1]+xWeights[2]) > 1e-6)
throw OpenMMException("LocalCoordinatesSite: Weights for computing x axis must add to 0");
if (fabs(yWeights[0]+yWeights[1]+yWeights[2]) > 1e-6)
throw OpenMMException("LocalCoordinatesSite: Weights for computing y axis must add to 0");
vector<int> particles(3);
particles[0] = particle1;
particles[1] = particle2;
particles[2] = particle3;
setParticles(particles);
}
const Vec3& LocalCoordinatesSite::getOriginWeights() const {
return originWeights;
}
const Vec3& LocalCoordinatesSite::getXWeights() const {
return xWeights;
}
const Vec3& LocalCoordinatesSite::getYWeights() const {
return yWeights;
}
const Vec3& LocalCoordinatesSite::getLocalPosition() const {
return localPosition;
}
......@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2012 Stanford University and the Authors. *
* Portions copyright (c) 2012-2014 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
......@@ -65,6 +65,24 @@ void ReferenceVirtualSites::computePositions(const OpenMM::System& system, vecto
RealVec cross = v12.cross(v13);
atomCoordinates[i] = atomCoordinates[p1] + v12*w12 + v13*w13 + cross*wcross;
}
else if (dynamic_cast<const LocalCoordinatesSite*>(&system.getVirtualSite(i)) != NULL) {
// A local coordinates site.
const LocalCoordinatesSite& site = dynamic_cast<const LocalCoordinatesSite&>(system.getVirtualSite(i));
int p1 = site.getParticle(0), p2 = site.getParticle(1), p3 = site.getParticle(2);
RealVec originWeights = site.getOriginWeights();
RealVec xWeights = site.getXWeights();
RealVec yWeights = site.getYWeights();
RealVec localPosition = site.getLocalPosition();
RealVec origin = atomCoordinates[p1]*originWeights[0] + atomCoordinates[p2]*originWeights[1] + atomCoordinates[p3]*originWeights[2];
RealVec xdir = atomCoordinates[p1]*xWeights[0] + atomCoordinates[p2]*xWeights[1] + atomCoordinates[p3]*xWeights[2];
RealVec ydir = atomCoordinates[p1]*yWeights[0] + atomCoordinates[p2]*yWeights[1] + atomCoordinates[p3]*yWeights[2];
RealVec zdir = xdir.cross(ydir);
xdir /= sqrt(xdir.dot(xdir));
zdir /= sqrt(zdir.dot(zdir));
ydir = zdir.cross(xdir);
atomCoordinates[i] = origin + xdir*localPosition[0] + ydir*localPosition[1] + zdir*localPosition[2];
}
}
}
......@@ -110,5 +128,75 @@ void ReferenceVirtualSites::distributeForces(const OpenMM::System& system, const
forces[p2] += f2;
forces[p3] += f3;
}
else if (dynamic_cast<const LocalCoordinatesSite*>(&system.getVirtualSite(i)) != NULL) {
// A local coordinates site.
const LocalCoordinatesSite& site = dynamic_cast<const LocalCoordinatesSite&>(system.getVirtualSite(i));
int p1 = site.getParticle(0), p2 = site.getParticle(1), p3 = site.getParticle(2);
RealVec originWeights = site.getOriginWeights();
RealVec wx = site.getXWeights();
RealVec wy = site.getYWeights();
RealVec localPosition = site.getLocalPosition();
RealVec xdir = atomCoordinates[p1]*wx[0] + atomCoordinates[p2]*wx[1] + atomCoordinates[p3]*wx[2];
RealVec ydir = atomCoordinates[p1]*wy[0] + atomCoordinates[p2]*wy[1] + atomCoordinates[p3]*wy[2];
RealVec zdir = xdir.cross(ydir);
RealOpenMM invNormXdir = 1.0/SQRT(xdir.dot(xdir));
RealOpenMM invNormZdir = 1.0/SQRT(zdir.dot(zdir));
RealVec dx = xdir*invNormXdir;
RealVec dz = zdir*invNormZdir;
RealVec dy = dz.cross(dx);
// The derivatives for this case are very complicated. They were computed with SymPy then simplified by hand.
RealOpenMM t11 = (wx[0]*ydir[0]-wy[0]*xdir[0])*invNormZdir;
RealOpenMM t12 = (wx[0]*ydir[1]-wy[0]*xdir[1])*invNormZdir;
RealOpenMM t13 = (wx[0]*ydir[2]-wy[0]*xdir[2])*invNormZdir;
RealOpenMM t21 = (wx[1]*ydir[0]-wy[1]*xdir[0])*invNormZdir;
RealOpenMM t22 = (wx[1]*ydir[1]-wy[1]*xdir[1])*invNormZdir;
RealOpenMM t23 = (wx[1]*ydir[2]-wy[1]*xdir[2])*invNormZdir;
RealOpenMM t31 = (wx[2]*ydir[0]-wy[2]*xdir[0])*invNormZdir;
RealOpenMM t32 = (wx[2]*ydir[1]-wy[2]*xdir[1])*invNormZdir;
RealOpenMM t33 = (wx[2]*ydir[2]-wy[2]*xdir[2])*invNormZdir;
RealOpenMM sx1 = t13*dz[1]-t12*dz[2];
RealOpenMM sy1 = t11*dz[2]-t13*dz[0];
RealOpenMM sz1 = t12*dz[0]-t11*dz[1];
RealOpenMM sx2 = t23*dz[1]-t22*dz[2];
RealOpenMM sy2 = t21*dz[2]-t23*dz[0];
RealOpenMM sz2 = t22*dz[0]-t21*dz[1];
RealOpenMM sx3 = t33*dz[1]-t32*dz[2];
RealOpenMM sy3 = t31*dz[2]-t33*dz[0];
RealOpenMM sz3 = t32*dz[0]-t31*dz[1];
RealVec wxScaled = wx*invNormXdir;
RealVec fp1 = localPosition*f[0];
RealVec fp2 = localPosition*f[1];
RealVec fp3 = localPosition*f[2];
forces[p1][0] += fp1[0]*wxScaled[0]*(1-dx[0]*dx[0]) + fp1[2]*(dz[0]*sx1 ) + fp1[1]*((-dx[0]*dy[0] )*wxScaled[0] + dy[0]*sx1 - dx[1]*t12 - dx[2]*t13) + f[0]*originWeights[0];
forces[p1][1] += fp1[0]*wxScaled[0]*( -dx[0]*dx[1]) + fp1[2]*(dz[0]*sy1+t13) + fp1[1]*((-dx[1]*dy[0]-dz[2])*wxScaled[0] + dy[0]*sy1 + dx[1]*t11);
forces[p1][2] += fp1[0]*wxScaled[0]*( -dx[0]*dx[2]) + fp1[2]*(dz[0]*sz1-t12) + fp1[1]*((-dx[2]*dy[0]+dz[1])*wxScaled[0] + dy[0]*sz1 + dx[2]*t11);
forces[p2][0] += fp1[0]*wxScaled[1]*(1-dx[0]*dx[0]) + fp1[2]*(dz[0]*sx2 ) + fp1[1]*((-dx[0]*dy[0] )*wxScaled[1] + dy[0]*sx2 - dx[1]*t22 - dx[2]*t23) + f[0]*originWeights[1];
forces[p2][1] += fp1[0]*wxScaled[1]*( -dx[0]*dx[1]) + fp1[2]*(dz[0]*sy2+t23) + fp1[1]*((-dx[1]*dy[0]-dz[2])*wxScaled[1] + dy[0]*sy2 + dx[1]*t21);
forces[p2][2] += fp1[0]*wxScaled[1]*( -dx[0]*dx[2]) + fp1[2]*(dz[0]*sz2-t22) + fp1[1]*((-dx[2]*dy[0]+dz[1])*wxScaled[1] + dy[0]*sz2 + dx[2]*t21);
forces[p3][0] += fp1[0]*wxScaled[2]*(1-dx[0]*dx[0]) + fp1[2]*(dz[0]*sx3 ) + fp1[1]*((-dx[0]*dy[0] )*wxScaled[2] + dy[0]*sx3 - dx[1]*t32 - dx[2]*t33) + f[0]*originWeights[2];
forces[p3][1] += fp1[0]*wxScaled[2]*( -dx[0]*dx[1]) + fp1[2]*(dz[0]*sy3+t33) + fp1[1]*((-dx[1]*dy[0]-dz[2])*wxScaled[2] + dy[0]*sy3 + dx[1]*t31);
forces[p3][2] += fp1[0]*wxScaled[2]*( -dx[0]*dx[2]) + fp1[2]*(dz[0]*sz3-t32) + fp1[1]*((-dx[2]*dy[0]+dz[1])*wxScaled[2] + dy[0]*sz3 + dx[2]*t31);
forces[p1][0] += fp2[0]*wxScaled[0]*( -dx[1]*dx[0]) + fp2[2]*(dz[1]*sx1-t13) - fp2[1]*(( dx[0]*dy[1]-dz[2])*wxScaled[0] - dy[1]*sx1 - dx[0]*t12);
forces[p1][1] += fp2[0]*wxScaled[0]*(1-dx[1]*dx[1]) + fp2[2]*(dz[1]*sy1 ) - fp2[1]*(( dx[1]*dy[1] )*wxScaled[0] - dy[1]*sy1 + dx[0]*t11 + dx[2]*t13) + f[1]*originWeights[0];
forces[p1][2] += fp2[0]*wxScaled[0]*( -dx[1]*dx[2]) + fp2[2]*(dz[1]*sz1+t11) - fp2[1]*(( dx[2]*dy[1]+dz[0])*wxScaled[0] - dy[1]*sz1 - dx[2]*t12);
forces[p2][0] += fp2[0]*wxScaled[1]*( -dx[1]*dx[0]) + fp2[2]*(dz[1]*sx2-t23) - fp2[1]*(( dx[0]*dy[1]-dz[2])*wxScaled[1] - dy[1]*sx2 - dx[0]*t22);
forces[p2][1] += fp2[0]*wxScaled[1]*(1-dx[1]*dx[1]) + fp2[2]*(dz[1]*sy2 ) - fp2[1]*(( dx[1]*dy[1] )*wxScaled[1] - dy[1]*sy2 + dx[0]*t21 + dx[2]*t23) + f[1]*originWeights[1];
forces[p2][2] += fp2[0]*wxScaled[1]*( -dx[1]*dx[2]) + fp2[2]*(dz[1]*sz2+t21) - fp2[1]*(( dx[2]*dy[1]+dz[0])*wxScaled[1] - dy[1]*sz2 - dx[2]*t22);
forces[p3][0] += fp2[0]*wxScaled[2]*( -dx[1]*dx[0]) + fp2[2]*(dz[1]*sx3-t33) - fp2[1]*(( dx[0]*dy[1]-dz[2])*wxScaled[2] - dy[1]*sx3 - dx[0]*t32);
forces[p3][1] += fp2[0]*wxScaled[2]*(1-dx[1]*dx[1]) + fp2[2]*(dz[1]*sy3 ) - fp2[1]*(( dx[1]*dy[1] )*wxScaled[2] - dy[1]*sy3 + dx[0]*t31 + dx[2]*t33) + f[1]*originWeights[2];
forces[p3][2] += fp2[0]*wxScaled[2]*( -dx[1]*dx[2]) + fp2[2]*(dz[1]*sz3+t31) - fp2[1]*(( dx[2]*dy[1]+dz[0])*wxScaled[2] - dy[1]*sz3 - dx[2]*t32);
forces[p1][0] += fp3[0]*wxScaled[0]*( -dx[2]*dx[0]) + fp3[2]*(dz[2]*sx1+t12) + fp3[1]*((-dx[0]*dy[2]-dz[1])*wxScaled[0] + dy[2]*sx1 + dx[0]*t13);
forces[p1][1] += fp3[0]*wxScaled[0]*( -dx[2]*dx[1]) + fp3[2]*(dz[2]*sy1-t11) + fp3[1]*((-dx[1]*dy[2]+dz[0])*wxScaled[0] + dy[2]*sy1 + dx[1]*t13);
forces[p1][2] += fp3[0]*wxScaled[0]*(1-dx[2]*dx[2]) + fp3[2]*(dz[2]*sz1 ) + fp3[1]*((-dx[2]*dy[2] )*wxScaled[0] + dy[2]*sz1 - dx[0]*t11 - dx[1]*t12) + f[2]*originWeights[0];
forces[p2][0] += fp3[0]*wxScaled[1]*( -dx[2]*dx[0]) + fp3[2]*(dz[2]*sx2+t22) + fp3[1]*((-dx[0]*dy[2]-dz[1])*wxScaled[1] + dy[2]*sx2 + dx[0]*t23);
forces[p2][1] += fp3[0]*wxScaled[1]*( -dx[2]*dx[1]) + fp3[2]*(dz[2]*sy2-t21) + fp3[1]*((-dx[1]*dy[2]+dz[0])*wxScaled[1] + dy[2]*sy2 + dx[1]*t23);
forces[p2][2] += fp3[0]*wxScaled[1]*(1-dx[2]*dx[2]) + fp3[2]*(dz[2]*sz2 ) + fp3[1]*((-dx[2]*dy[2] )*wxScaled[1] + dy[2]*sz2 - dx[0]*t21 - dx[1]*t22) + f[2]*originWeights[1];
forces[p3][0] += fp3[0]*wxScaled[2]*( -dx[2]*dx[0]) + fp3[2]*(dz[2]*sx3+t32) + fp3[1]*((-dx[0]*dy[2]-dz[1])*wxScaled[2] + dy[2]*sx3 + dx[0]*t33);
forces[p3][1] += fp3[0]*wxScaled[2]*( -dx[2]*dx[1]) + fp3[2]*(dz[2]*sy3-t31) + fp3[1]*((-dx[1]*dy[2]+dz[0])*wxScaled[2] + dy[2]*sy3 + dx[1]*t33);
forces[p3][2] += fp3[0]*wxScaled[2]*(1-dx[2]*dx[2]) + fp3[2]*(dz[2]*sz3 ) + fp3[1]*((-dx[2]*dy[2] )*wxScaled[2] + dy[2]*sz3 - dx[0]*t31 - dx[1]*t32) + f[2]*originWeights[2];
}
}
}
......@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* Portions copyright (c) 2012 Stanford University and the Authors. *
* Portions copyright (c) 2012-2014 Stanford University and the Authors. *
* Authors: Peter Eastman *
* Contributors: *
* *
......@@ -215,6 +215,85 @@ void testOutOfPlane() {
}
}
/**
* Test a LocalCoordinatesSite virtual site.
*/
void testLocalCoordinates() {
const Vec3 originWeights(0.2, 0.3, 0.5);
const Vec3 xWeights(-1.0, 0.5, 0.5);
const Vec3 yWeights(0.0, -1.0, 1.0);
const Vec3 localPosition(0.4, 0.3, 0.2);
ReferencePlatform platform;
System system;
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(0.0);
system.setVirtualSite(3, new LocalCoordinatesSite(0, 1, 2, originWeights, xWeights, yWeights, localPosition));
CustomExternalForce* forceField = new CustomExternalForce("2*x^2+3*y^2+4*z^2");
system.addForce(forceField);
vector<double> params;
forceField->addParticle(0, params);
forceField->addParticle(1, params);
forceField->addParticle(2, params);
forceField->addParticle(3, params);
LangevinIntegrator integrator(300.0, 0.1, 0.002);
Context context(system, integrator, platform);
vector<Vec3> positions(4), positions2(4), positions3(4);
OpenMM_SFMT::SFMT sfmt;
init_gen_rand(0, sfmt);
for (int i = 0; i < 100; i++) {
// Set the particles at random positions.
Vec3 xdir, ydir, zdir;
do {
for (int j = 0; j < 3; j++)
positions[j] = Vec3(genrand_real2(sfmt), genrand_real2(sfmt), genrand_real2(sfmt));
xdir = positions[0]*xWeights[0] + positions[1]*xWeights[1] + positions[2]*xWeights[2];
ydir = positions[0]*yWeights[0] + positions[1]*yWeights[1] + positions[2]*yWeights[2];
zdir = xdir.cross(ydir);
if (sqrt(xdir.dot(xdir)) > 0.1 && sqrt(ydir.dot(ydir)) > 0.1 && sqrt(zdir.dot(zdir)) > 0.1)
break; // These positions give a reasonable coordinate system.
} while (true);
context.setPositions(positions);
context.applyConstraints(0.0001);
// See if the virtual site is positioned correctly.
State state = context.getState(State::Positions | State::Forces);
const vector<Vec3>& pos = state.getPositions();
Vec3 origin = pos[0]*originWeights[0] + pos[1]*originWeights[1] + pos[2]*originWeights[2];
xdir /= sqrt(xdir.dot(xdir));
zdir /= sqrt(zdir.dot(zdir));
ydir = zdir.cross(xdir);
ASSERT_EQUAL_VEC(origin+xdir*localPosition[0]+ydir*localPosition[1]+zdir*localPosition[2], pos[3], 1e-10);
// Take a small step in the direction of the energy gradient and see whether the potential energy changes by the expected amount.
double norm = 0.0;
for (int i = 0; i < 3; ++i) {
Vec3 f = state.getForces()[i];
norm += f[0]*f[0] + f[1]*f[1] + f[2]*f[2];
}
norm = std::sqrt(norm);
const double delta = 1e-2;
double step = 0.5*delta/norm;
for (int i = 0; i < 3; ++i) {
Vec3 p = positions[i];
Vec3 f = state.getForces()[i];
positions2[i] = Vec3(p[0]-f[0]*step, p[1]-f[1]*step, p[2]-f[2]*step);
positions3[i] = Vec3(p[0]+f[0]*step, p[1]+f[1]*step, p[2]+f[2]*step);
}
context.setPositions(positions2);
context.applyConstraints(0.0001);
State state2 = context.getState(State::Energy);
context.setPositions(positions3);
context.applyConstraints(0.0001);
State state3 = context.getState(State::Energy);
ASSERT_EQUAL_TOL(norm, (state2.getPotentialEnergy()-state3.getPotentialEnergy())/delta, 1e-3)
}
}
/**
* Make sure that energy, linear momentum, and angular momentum are all conserved
* when using virtual sites.
......@@ -281,6 +360,26 @@ void testConservationLaws() {
positions.push_back(Vec3(2, 0, -1));
positions.push_back(Vec3(1, 1, -1));
positions.push_back(Vec3());
// Create a molecule with a LocalCoordinatesSite virtual site.
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(1.0);
system.addParticle(0.0);
system.setVirtualSite(14, new LocalCoordinatesSite(11, 12, 13, Vec3(0.3, 0.3, 0.4), Vec3(1.0, -0.5, -0.5), Vec3(0, -1.0, 1.0), Vec3(0.2, 0.2, 1.0)));
system.addConstraint(11, 12, 1.0);
system.addConstraint(11, 13, 1.0);
system.addConstraint(12, 13, sqrt(2.0));
for (int i = 0; i < 4; i++) {
forceField->addParticle(0, 1, 10);
for (int j = 0; j < i; j++)
forceField->addException(i+11, j+11, 0, 1, 0);
}
positions.push_back(Vec3(1, 2, 0));
positions.push_back(Vec3(2, 2, 0));
positions.push_back(Vec3(1, 3, 0));
positions.push_back(Vec3());
// Simulate it and check conservation laws.
......@@ -329,6 +428,7 @@ int main() {
testTwoParticleAverage();
testThreeParticleAverage();
testOutOfPlane();
testLocalCoordinates();
testConservationLaws();
}
catch(const exception& e) {
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment