Commit 90f31a68 authored by Christopher Bruns's avatar Christopher Bruns
Browse files

Many changes to build OpenMM on Windows:

1) Added DLL export macros to many classes; enough to get tests to compile
2) Cosmetic changes to reduce warnings in Visual Studio:
   a) change many "for (int i ="... to "for (size_t i ="... to avoid unsigned mismatch warnings
   b) included many explicit static_cast<OpenMMReal>() casts to document places where double->float conversion warnings occur
parent 3353081f
......@@ -34,6 +34,7 @@
#include "Platform.h"
#include "ReferenceStreamFactory.h"
#include "internal/windowsExport.h"
namespace OpenMM {
......@@ -41,7 +42,7 @@ namespace OpenMM {
* This Platform subclass uses the reference implementations of all the OpenMM kernels.
*/
class ReferencePlatform : public Platform {
class OPENMM_EXPORT ReferencePlatform : public Platform {
public:
ReferencePlatform();
std::string getName() const {
......
......@@ -33,6 +33,7 @@
* -------------------------------------------------------------------------- */
#include "StreamFactory.h"
#include "internal/windowsExport.h"
namespace OpenMM {
......@@ -40,7 +41,7 @@ namespace OpenMM {
* This StreamFactory creates all streams for ReferencePlatform.
*/
class ReferenceStreamFactory : public StreamFactory {
class OPENMM_EXPORT ReferenceStreamFactory : public StreamFactory {
public:
StreamImpl* createStreamImpl(std::string name, int size, Stream::DataType type, const Platform& platform, OpenMMContextImpl& context) const;
};
......
......@@ -86,7 +86,7 @@ void ReferenceFloatStreamImpl::loadFromArray(const void* array) {
double* arrayData = (double*) array;
for (int i = 0; i < getSize(); ++i)
for (int j = 0; j < width; ++j)
data[i][j] = arrayData[i*width+j];
data[i][j] = static_cast<RealOpenMM>( arrayData[i*width+j] );
}
}
......@@ -116,7 +116,7 @@ void ReferenceFloatStreamImpl::fillWithValue(void* value) {
double valueData = *((double*) value);
for (int i = 0; i < getSize(); ++i)
for (int j = 0; j < width; ++j)
data[i][j] = valueData;
data[i][j] = static_cast<RealOpenMM>(valueData);
}
}
......
......@@ -31,24 +31,28 @@
#include "ReferenceKernelFactory.h"
#include "ReferenceKernels.h"
#include "OpenMMException.h"
using namespace OpenMM;
KernelImpl* ReferenceKernelFactory::createKernelImpl(std::string name, const Platform& platform, OpenMMContextImpl& context) const {
if (name == CalcStandardMMForceFieldKernel::Name())
return new ReferenceCalcStandardMMForceFieldKernel(name, platform);
if (name == CalcGBSAOBCForceFieldKernel::Name())
else if (name == CalcGBSAOBCForceFieldKernel::Name())
return new ReferenceCalcGBSAOBCForceFieldKernel(name, platform);
if (name == IntegrateVerletStepKernel::Name())
else if (name == IntegrateVerletStepKernel::Name())
return new ReferenceIntegrateVerletStepKernel(name, platform);
if (name == IntegrateLangevinStepKernel::Name())
else if (name == IntegrateLangevinStepKernel::Name())
return new ReferenceIntegrateLangevinStepKernel(name, platform);
if (name == IntegrateBrownianStepKernel::Name())
else if (name == IntegrateBrownianStepKernel::Name())
return new ReferenceIntegrateBrownianStepKernel(name, platform);
if (name == ApplyAndersenThermostatKernel::Name())
else if (name == ApplyAndersenThermostatKernel::Name())
return new ReferenceApplyAndersenThermostatKernel(name, platform);
if (name == CalcKineticEnergyKernel::Name())
else if (name == CalcKineticEnergyKernel::Name())
return new ReferenceCalcKineticEnergyKernel(name, platform);
if (name == RemoveCMMotionKernel::Name())
else if (name == RemoveCMMotionKernel::Name())
return new ReferenceRemoveCMMotionKernel(name, platform);
else {
throw OpenMMException( (std::string("Tried to create kernel with illegal kernel name '") + name + "'").c_str() );
}
}
......@@ -68,8 +68,8 @@ int** copyToArray(const vector<vector<int> > vec) {
if (vec.size() == 0)
return new int*[0];
int** array = allocateIntArray(vec.size(), vec[0].size());
for (int i = 0; i < vec.size(); ++i)
for (int j = 0; j < vec[i].size(); ++j)
for (size_t i = 0; i < vec.size(); ++i)
for (size_t j = 0; j < vec[i].size(); ++j)
array[i][j] = vec[i][j];
return array;
}
......@@ -78,9 +78,9 @@ RealOpenMM** copyToArray(const vector<vector<double> > vec) {
if (vec.size() == 0)
return new RealOpenMM*[0];
RealOpenMM** array = allocateRealArray(vec.size(), vec[0].size());
for (int i = 0; i < vec.size(); ++i)
for (int j = 0; j < vec[i].size(); ++j)
array[i][j] = vec[i][j];
for (size_t i = 0; i < vec.size(); ++i)
for (size_t j = 0; j < vec[i].size(); ++j)
array[i][j] = static_cast<RealOpenMM>(vec[i][j]);
return array;
}
......@@ -136,11 +136,11 @@ void ReferenceCalcStandardMMForceFieldKernel::initialize(const vector<vector<int
rbTorsionIndexArray = copyToArray(rbTorsionIndices);
rbTorsionParamArray = copyToArray(rbTorsionParameters);
atomParamArray = allocateRealArray(numAtoms, 3);
RealOpenMM sqrtEps = std::sqrt(138.935485);
RealOpenMM sqrtEps = static_cast<RealOpenMM>( std::sqrt(138.935485) );
for (int i = 0; i < numAtoms; ++i) {
atomParamArray[i][0] = 0.5*nonbondedParameters[i][1];
atomParamArray[i][1] = 2.0*sqrt(nonbondedParameters[i][2]);
atomParamArray[i][2] = nonbondedParameters[i][0]*sqrtEps;
atomParamArray[i][0] = static_cast<RealOpenMM>( 0.5*nonbondedParameters[i][1] );
atomParamArray[i][1] = static_cast<RealOpenMM>( 2.0*sqrt(nonbondedParameters[i][2]) );
atomParamArray[i][2] = static_cast<RealOpenMM>( nonbondedParameters[i][0]*sqrtEps );
}
exclusionArray = new int*[numAtoms];
for (int i = 0; i < numAtoms; ++i) {
......@@ -155,9 +155,9 @@ void ReferenceCalcStandardMMForceFieldKernel::initialize(const vector<vector<int
for (int i = 0; i < num14; ++i) {
int atom1 = bonded14Indices[i][0];
int atom2 = bonded14Indices[i][1];
bonded14ParamArray[i][0] = atomParamArray[atom1][0]+atomParamArray[atom2][0];
bonded14ParamArray[i][1] = lj14Scale*(atomParamArray[atom1][1]*atomParamArray[atom2][1]);
bonded14ParamArray[i][2] = coulomb14Scale*(atomParamArray[atom1][2]*atomParamArray[atom2][2]);
bonded14ParamArray[i][0] = static_cast<RealOpenMM>( atomParamArray[atom1][0]+atomParamArray[atom2][0] );
bonded14ParamArray[i][1] = static_cast<RealOpenMM>( lj14Scale*(atomParamArray[atom1][1]*atomParamArray[atom2][1]) );
bonded14ParamArray[i][2] = static_cast<RealOpenMM>( coulomb14Scale*(atomParamArray[atom1][2]*atomParamArray[atom2][2]) );
}
}
......@@ -226,15 +226,15 @@ void ReferenceCalcGBSAOBCForceFieldKernel::initialize(const vector<vector<double
vector<RealOpenMM> atomicRadii(numAtoms);
vector<RealOpenMM> scaleFactors(numAtoms);
for (int i = 0; i < numAtoms; ++i) {
charges[i] = atomParameters[i][0];
atomicRadii[i] = atomParameters[i][1];
scaleFactors[i] = atomParameters[i][2];
charges[i] = static_cast<RealOpenMM>( atomParameters[i][0] );
atomicRadii[i] = static_cast<RealOpenMM>( atomParameters[i][1] );
scaleFactors[i] = static_cast<RealOpenMM>( atomParameters[i][2] );
}
ObcParameters* obcParameters = new ObcParameters(numAtoms, ObcParameters::ObcTypeII);
obcParameters->setAtomicRadii(atomicRadii, SimTKOpenMMCommon::MdUnits);
obcParameters->setScaledRadiusFactors(scaleFactors);
obcParameters->setSolventDielectric(solventDielectric);
obcParameters->setSoluteDielectric(soluteDielectric);
obcParameters->setSolventDielectric( static_cast<RealOpenMM>(solventDielectric) );
obcParameters->setSoluteDielectric( static_cast<RealOpenMM>(soluteDielectric) );
obc = new CpuObc(obcParameters);
obc->setIncludeAceApproximation(true);
}
......@@ -269,8 +269,8 @@ ReferenceIntegrateVerletStepKernel::~ReferenceIntegrateVerletStepKernel() {
void ReferenceIntegrateVerletStepKernel::initialize(const vector<double>& masses, const vector<vector<int> >& constraintIndices,
const vector<double>& constraintLengths) {
this->masses = new RealOpenMM[masses.size()];
for (int i = 0; i < masses.size(); ++i)
this->masses[i] = masses[i];
for (size_t i = 0; i < masses.size(); ++i)
this->masses[i] = static_cast<RealOpenMM>( masses[i] );
numConstraints = constraintIndices.size();
this->constraintIndices = allocateIntArray(numConstraints, 2);
for (int i = 0; i < numConstraints; ++i) {
......@@ -278,8 +278,8 @@ void ReferenceIntegrateVerletStepKernel::initialize(const vector<double>& masses
this->constraintIndices[i][1] = constraintIndices[i][1];
}
shakeParameters = allocateRealArray(constraintLengths.size(), 1);
for (int i = 0; i < constraintLengths.size(); ++i)
shakeParameters[i][0] = constraintLengths[i];
for (size_t i = 0; i < constraintLengths.size(); ++i)
shakeParameters[i][0] = static_cast<RealOpenMM>( constraintLengths[i] );
}
void ReferenceIntegrateVerletStepKernel::execute(Stream& positions, Stream& velocities, const Stream& forces, double stepSize) {
......@@ -293,7 +293,7 @@ void ReferenceIntegrateVerletStepKernel::execute(Stream& positions, Stream& velo
delete dynamics;
delete shake;
}
dynamics = new ReferenceVerletDynamics(positions.getSize(), stepSize);
dynamics = new ReferenceVerletDynamics(positions.getSize(), static_cast<RealOpenMM>(stepSize) );
shake = new ReferenceShakeAlgorithm(numConstraints, constraintIndices, shakeParameters);
dynamics->setReferenceShakeAlgorithm(shake);
prevStepSize = stepSize;
......@@ -317,8 +317,8 @@ ReferenceIntegrateLangevinStepKernel::~ReferenceIntegrateLangevinStepKernel() {
void ReferenceIntegrateLangevinStepKernel::initialize(const vector<double>& masses, const vector<vector<int> >& constraintIndices,
const vector<double>& constraintLengths) {
this->masses = new RealOpenMM[masses.size()];
for (int i = 0; i < masses.size(); ++i)
this->masses[i] = masses[i];
for (size_t i = 0; i < masses.size(); ++i)
this->masses[i] = static_cast<RealOpenMM>( masses[i] );
numConstraints = constraintIndices.size();
this->constraintIndices = allocateIntArray(numConstraints, 2);
for (int i = 0; i < numConstraints; ++i) {
......@@ -326,8 +326,8 @@ void ReferenceIntegrateLangevinStepKernel::initialize(const vector<double>& mass
this->constraintIndices[i][1] = constraintIndices[i][1];
}
shakeParameters = allocateRealArray(constraintLengths.size(), 1);
for (int i = 0; i < constraintLengths.size(); ++i)
shakeParameters[i][0] = constraintLengths[i];
for (size_t i = 0; i < constraintLengths.size(); ++i)
shakeParameters[i][0] = static_cast<RealOpenMM>( constraintLengths[i] );
}
void ReferenceIntegrateLangevinStepKernel::execute(Stream& positions, Stream& velocities, const Stream& forces, double temperature, double friction, double stepSize) {
......@@ -341,8 +341,12 @@ void ReferenceIntegrateLangevinStepKernel::execute(Stream& positions, Stream& ve
delete dynamics;
delete shake;
}
RealOpenMM tau = (friction == 0.0 ? 0.0 : 1.0/friction);
dynamics = new ReferenceStochasticDynamics(positions.getSize(), stepSize, tau, temperature);
RealOpenMM tau = static_cast<RealOpenMM>( friction == 0.0 ? 0.0 : 1.0/friction );
dynamics = new ReferenceStochasticDynamics(
positions.getSize(),
static_cast<RealOpenMM>(stepSize),
static_cast<RealOpenMM>(tau),
static_cast<RealOpenMM>(temperature) );
shake = new ReferenceShakeAlgorithm(numConstraints, constraintIndices, shakeParameters);
dynamics->setReferenceShakeAlgorithm(shake);
prevTemp = temperature;
......@@ -368,8 +372,8 @@ ReferenceIntegrateBrownianStepKernel::~ReferenceIntegrateBrownianStepKernel() {
void ReferenceIntegrateBrownianStepKernel::initialize(const vector<double>& masses, const vector<vector<int> >& constraintIndices,
const vector<double>& constraintLengths) {
this->masses = new RealOpenMM[masses.size()];
for (int i = 0; i < masses.size(); ++i)
this->masses[i] = masses[i];
for (size_t i = 0; i < masses.size(); ++i)
this->masses[i] = static_cast<RealOpenMM>(masses[i]);
numConstraints = constraintIndices.size();
this->constraintIndices = allocateIntArray(numConstraints, 2);
for (int i = 0; i < numConstraints; ++i) {
......@@ -377,8 +381,8 @@ void ReferenceIntegrateBrownianStepKernel::initialize(const vector<double>& mass
this->constraintIndices[i][1] = constraintIndices[i][1];
}
shakeParameters = allocateRealArray(constraintLengths.size(), 1);
for (int i = 0; i < constraintLengths.size(); ++i)
shakeParameters[i][0] = constraintLengths[i];
for (size_t i = 0; i < constraintLengths.size(); ++i)
shakeParameters[i][0] = static_cast<RealOpenMM>( constraintLengths[i] );
}
void ReferenceIntegrateBrownianStepKernel::execute(Stream& positions, Stream& velocities, const Stream& forces, double temperature, double friction, double stepSize) {
......@@ -392,7 +396,11 @@ void ReferenceIntegrateBrownianStepKernel::execute(Stream& positions, Stream& ve
delete dynamics;
delete shake;
}
dynamics = new ReferenceBrownianDynamics(positions.getSize(), stepSize, friction, temperature);
dynamics = new ReferenceBrownianDynamics(
positions.getSize(),
static_cast<RealOpenMM>(stepSize),
static_cast<RealOpenMM>(friction),
static_cast<RealOpenMM>(temperature) );
shake = new ReferenceShakeAlgorithm(numConstraints, constraintIndices, shakeParameters);
dynamics->setReferenceShakeAlgorithm(shake);
prevTemp = temperature;
......@@ -411,14 +419,20 @@ ReferenceApplyAndersenThermostatKernel::~ReferenceApplyAndersenThermostatKernel(
void ReferenceApplyAndersenThermostatKernel::initialize(const vector<double>& masses) {
this->masses = new RealOpenMM[masses.size()];
for (int i = 0; i < masses.size(); ++i)
this->masses[i] = masses[i];
for (size_t i = 0; i < masses.size(); ++i)
this->masses[i] = static_cast<RealOpenMM>( masses[i] );
thermostat = new ReferenceAndersenThermostat();
}
void ReferenceApplyAndersenThermostatKernel::execute(Stream& velocities, double temperature, double collisionFrequency, double stepSize) {
RealOpenMM** velData = ((ReferenceFloatStreamImpl&) velocities.getImpl()).getData();
thermostat->applyThermostat(velocities.getSize(), velData, masses, temperature, collisionFrequency, stepSize);
thermostat->applyThermostat(
velocities.getSize(),
velData,
masses,
static_cast<RealOpenMM>(temperature),
static_cast<RealOpenMM>(collisionFrequency),
static_cast<RealOpenMM>(stepSize) );
}
void ReferenceCalcKineticEnergyKernel::initialize(const vector<double>& masses) {
......@@ -428,14 +442,14 @@ void ReferenceCalcKineticEnergyKernel::initialize(const vector<double>& masses)
double ReferenceCalcKineticEnergyKernel::execute(const Stream& velocities) {
RealOpenMM** velData = const_cast<RealOpenMM**>(((ReferenceFloatStreamImpl&) velocities.getImpl()).getData()); // Reference code needs to be made const correct
double energy = 0.0;
for (int i = 0; i < masses.size(); ++i)
for (size_t i = 0; i < masses.size(); ++i)
energy += masses[i]*(velData[i][0]*velData[i][0]+velData[i][1]*velData[i][1]+velData[i][2]*velData[i][2]);
return 0.5*energy;
}
void ReferenceRemoveCMMotionKernel::initialize(const vector<double>& masses) {
this->masses.resize(masses.size());
for (int i = 0; i < masses.size(); ++i)
for (size_t i = 0; i < masses.size(); ++i)
this->masses[i] = masses[i];
}
......@@ -445,20 +459,20 @@ void ReferenceRemoveCMMotionKernel::execute(Stream& velocities) {
// Calculate the center of mass momentum.
RealOpenMM momentum[] = {0.0, 0.0, 0.0};
for (int i = 0; i < masses.size(); ++i) {
momentum[0] += masses[i]*velData[i][0];
momentum[1] += masses[i]*velData[i][1];
momentum[2] += masses[i]*velData[i][2];
for (size_t i = 0; i < masses.size(); ++i) {
momentum[0] += static_cast<RealOpenMM>( masses[i]*velData[i][0] );
momentum[1] += static_cast<RealOpenMM>( masses[i]*velData[i][1] );
momentum[2] += static_cast<RealOpenMM>( masses[i]*velData[i][2] );
}
// Adjust the atom velocities.
momentum[0] /= masses.size();
momentum[1] /= masses.size();
momentum[2] /= masses.size();
for (int i = 0; i < masses.size(); ++i) {
velData[i][0] -= momentum[0]/masses[i];
velData[i][1] -= momentum[1]/masses[i];
velData[i][2] -= momentum[2]/masses[i];
momentum[0] /= static_cast<RealOpenMM>( masses.size() );
momentum[1] /= static_cast<RealOpenMM>( masses.size() );
momentum[2] /= static_cast<RealOpenMM>( masses.size() );
for (size_t i = 0; i < masses.size(); ++i) {
velData[i][0] -= static_cast<RealOpenMM>( momentum[0]/masses[i] );
velData[i][1] -= static_cast<RealOpenMM>( momentum[1]/masses[i] );
velData[i][2] -= static_cast<RealOpenMM>( momentum[2]/masses[i] );
}
}
......@@ -67,7 +67,7 @@
// A collision occurred, so set the velocity to a new value chosen from a Boltzmann distribution.
const RealOpenMM velocityScale = sqrt(BOLTZ*temperature/atomMasses[i]);
const RealOpenMM velocityScale = static_cast<RealOpenMM>( sqrt(BOLTZ*temperature/atomMasses[i]) );
atomVelocities[i][0] = velocityScale*SimTKOpenMMUtilities::getNormallyDistributedRandomNumber();
atomVelocities[i][1] = velocityScale*SimTKOpenMMUtilities::getNormallyDistributedRandomNumber();
atomVelocities[i][2] = velocityScale*SimTKOpenMMUtilities::getNormallyDistributedRandomNumber();
......
......@@ -196,7 +196,7 @@ int ReferenceBrownianDynamics::update( int numberOfAtoms, RealOpenMM** atomCoord
// Perform the integration.
const RealOpenMM noiseAmplitude = sqrt(2.0*BOLTZ*getTemperature()*getDeltaT()/getFriction());
const RealOpenMM noiseAmplitude = static_cast<RealOpenMM>( sqrt(2.0*BOLTZ*getTemperature()*getDeltaT()/getFriction()) );
const RealOpenMM forceScale = getDeltaT()/getFriction();
for (int i = 0; i < numberOfAtoms; ++i) {
for (int j = 0; j < 3; ++j) {
......@@ -209,7 +209,7 @@ int ReferenceBrownianDynamics::update( int numberOfAtoms, RealOpenMM** atomCoord
// Update the positions and velocities.
RealOpenMM velocityScale = 1.0/getDeltaT();
RealOpenMM velocityScale = static_cast<RealOpenMM>( 1.0/getDeltaT() );
for (int i = 0; i < numberOfAtoms; ++i) {
for (int j = 0; j < 3; ++j) {
velocities[i][j] = velocityScale*(xPrime[i][j] - atomCoordinates[i][j]);
......
......@@ -685,7 +685,7 @@ int ReferenceDynamics::writeState( int numberOfAtoms, RealOpenMM** atomCoordinat
scalarR.push_back( getDeltaT() );
scalarNameR.push_back( "delta_t" );
scalarR.push_back( SimTKOpenMMUtilities::getRandomNumberSeed() );
scalarR.push_back( static_cast<RealOpenMM>(SimTKOpenMMUtilities::getRandomNumberSeed()) );
scalarNameR.push_back( "seed" );
scalarR.push_back( getTemperature() );
......
......@@ -176,7 +176,7 @@ int ReferenceVerletDynamics::update( int numberOfAtoms, RealOpenMM** atomCoordin
// Update the positions and velocities.
RealOpenMM velocityScale = 1.0/getDeltaT();
RealOpenMM velocityScale = static_cast<RealOpenMM>( 1.0/getDeltaT() );
for (int i = 0; i < numberOfAtoms; ++i) {
for (int j = 0; j < 3; ++j) {
velocities[i][j] = velocityScale*(xPrime[i][j] - atomCoordinates[i][j]);
......
......@@ -1443,11 +1443,11 @@ RealOpenMM SimTKOpenMMUtilities::getNormallyDistributedRandomNumber( void ) {
RealOpenMM x, y, r2;
do {
x = 2.0*genrand_real2()-1.0;
y = 2.0*genrand_real2()-1.0;
x = static_cast<RealOpenMM>(2.0 * genrand_real2() - 1.0);
y = static_cast<RealOpenMM>(2.0 * genrand_real2() - 1.0);
r2 = x*x + y*y;
} while (r2 >= 1.0 || r2 == 0.0);
RealOpenMM multiplier = sqrt((-2.0*log(r2))/r2);
RealOpenMM multiplier = static_cast<RealOpenMM>( sqrt((-2.0*log(r2))/r2) );
nextValue = y*multiplier;
nextValueIsValid = true;
return x*multiplier;
......@@ -1466,7 +1466,7 @@ RealOpenMM SimTKOpenMMUtilities::getUniformlyDistributedRandomNumber( void ) {
init_gen_rand(_randomNumberSeed);
_randomInitialized = true;
}
return genrand_real2();
return static_cast<RealOpenMM>( genrand_real2() );
}
/**---------------------------------------------------------------------------------------
......
......@@ -32,6 +32,7 @@
#define SFMT_H
#include <stdio.h>
#include "internal/windowsExport.h"
#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
#include <inttypes.h>
......@@ -72,11 +73,14 @@
#define PRE_ALWAYS inline
#endif
uint32_t gen_rand32(void);
uint64_t gen_rand64(void);
uint32_t OPENMM_EXPORT gen_rand32(void);
uint64_t OPENMM_EXPORT gen_rand64(void);
void fill_array32(uint32_t *array, int size);
void fill_array64(uint64_t *array, int size);
void init_gen_rand(uint32_t seed);
void OPENMM_EXPORT init_gen_rand(uint32_t seed);
void init_by_array(uint32_t *init_key, int key_length);
const char *get_idstring(void);
int get_min_array_size32(void);
......
......@@ -120,7 +120,7 @@ void verify14(const vector<vector<int> >& bonded14Indices) {
addAtomsTo14List(i+1, i+4, expected);
}
ASSERT_EQUAL(expected.size(), bonded14Indices.size());
for (int i = 0; i < bonded14Indices.size(); ++i) {
for (size_t i = 0; i < bonded14Indices.size(); ++i) {
int atom1 = bonded14Indices[i][0];
int atom2 = bonded14Indices[i][1];
found.insert(pair<int, int>(min(atom1, atom2), max(atom1, atom2)));
......@@ -152,6 +152,7 @@ public:
void executeForces(const Stream& positions, Stream& forces) {
}
double executeEnergy(const Stream& positions) {
return 0.0;
}
};
......
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