Skip to content
GitLab
Menu
Projects
Groups
Snippets
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Menu
Open sidebar
tsoc
openmm
Commits
0e2ffb4b
Commit
0e2ffb4b
authored
Aug 14, 2014
by
peastman
Browse files
CPU version of CustomManyParticleForce is multithreaded
parent
1bc8e327
Changes
6
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
906 additions
and
187 deletions
+906
-187
platforms/cpu/include/CpuCustomManyParticleForce.h
platforms/cpu/include/CpuCustomManyParticleForce.h
+53
-39
platforms/cpu/src/CpuCustomManyParticleForce.cpp
platforms/cpu/src/CpuCustomManyParticleForce.cpp
+263
-144
platforms/cpu/src/CpuKernels.cpp
platforms/cpu/src/CpuKernels.cpp
+2
-3
platforms/cpu/tests/TestCpuCustomManyParticleForce.cpp
platforms/cpu/tests/TestCpuCustomManyParticleForce.cpp
+519
-0
platforms/reference/src/SimTKReference/ReferenceCustomManyParticleIxn.cpp
...nce/src/SimTKReference/ReferenceCustomManyParticleIxn.cpp
+1
-1
platforms/reference/tests/TestReferenceCustomManyParticleForce.cpp
.../reference/tests/TestReferenceCustomManyParticleForce.cpp
+68
-0
No files found.
platforms/cpu/include/CpuCustomManyParticleForce.h
View file @
0e2ffb4b
...
@@ -36,6 +36,7 @@
...
@@ -36,6 +36,7 @@
#include "lepton/ParsedExpression.h"
#include "lepton/ParsedExpression.h"
#include <map>
#include <map>
#include <set>
#include <set>
#include <utility>
#include <vector>
#include <vector>
namespace
OpenMM
{
namespace
OpenMM
{
...
@@ -47,26 +48,36 @@ private:
...
@@ -47,26 +48,36 @@ private:
class
DistanceTermInfo
;
class
DistanceTermInfo
;
class
AngleTermInfo
;
class
AngleTermInfo
;
class
DihedralTermInfo
;
class
DihedralTermInfo
;
class
ComputeForceTask
;
class
ThreadData
;
int
numParticlesPerSet
,
numPerParticleParameters
,
numTypes
;
int
numParticlesPerSet
,
numPerParticleParameters
,
numTypes
;
bool
useCutoff
,
usePeriodic
;
bool
useCutoff
,
usePeriodic
;
RealOpenMM
cutoffDistance
;
RealOpenMM
cutoffDistance
;
RealOpenMM
periodicBoxSize
[
3
];
RealOpenMM
periodicBoxSize
[
3
];
CpuNeighborList
*
neighborList
;
CpuNeighborList
*
neighborList
;
ThreadPool
&
threads
;
ThreadPool
&
threads
;
CompiledExpressionSet
expressionSet
;
Lepton
::
CompiledExpression
energyExpression
;
std
::
vector
<
std
::
vector
<
int
>
>
particleParamIndices
;
std
::
vector
<
std
::
set
<
int
>
>
exclusions
;
std
::
vector
<
std
::
set
<
int
>
>
exclusions
;
std
::
vector
<
int
>
particleTypes
;
std
::
vector
<
int
>
particleTypes
;
std
::
vector
<
int
>
orderIndex
;
std
::
vector
<
int
>
orderIndex
;
std
::
vector
<
std
::
vector
<
int
>
>
particleOrder
;
std
::
vector
<
std
::
vector
<
int
>
>
particleOrder
;
std
::
vector
<
ParticleTermInfo
>
particleTerms
;
std
::
vector
<
ThreadData
*>
threadData
;
std
::
vector
<
DistanceTermInfo
>
distanceTerms
;
// The following variables are used to make information accessible to the individual threads.
std
::
vector
<
AngleTermInfo
>
angleTerms
;
int
numParticles
;
std
::
vector
<
DihedralTermInfo
>
dihedralTerms
;
float
*
posq
;
RealVec
const
*
atomCoordinates
;
RealOpenMM
**
particleParameters
;
const
std
::
map
<
std
::
string
,
double
>*
globalParameters
;
std
::
vector
<
AlignedArray
<
float
>
>*
threadForce
;
bool
includeForces
,
includeEnergy
;
void
*
atomicCounter
;
/**
* This routine contains the code executed by each thread.
*/
void
threadComputeForce
(
ThreadPool
&
threads
,
int
threadIndex
);
void
loopOverInteractions
(
std
::
vector
<
int
>&
availableParticles
,
std
::
vector
<
int
>&
particleSet
,
int
loopIndex
,
int
startIndex
,
float
*
posq
,
std
::
vector
<
OpenMM
::
RealVec
>&
atomCoordinates
,
void
loopOverInteractions
(
std
::
vector
<
int
>&
availableParticles
,
std
::
vector
<
int
>&
particleSet
,
int
loopIndex
,
int
startIndex
,
RealOpenMM
**
particleParameters
,
std
::
vector
<
OpenMM
::
RealVec
>&
forces
,
RealOpenMM
*
totalEnergy
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
);
RealOpenMM
**
particleParameters
,
float
*
forces
,
ThreadData
&
data
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
);
/**---------------------------------------------------------------------------------------
/**---------------------------------------------------------------------------------------
...
@@ -81,8 +92,8 @@ private:
...
@@ -81,8 +92,8 @@ private:
--------------------------------------------------------------------------------------- */
--------------------------------------------------------------------------------------- */
void
calculateOneIxn
(
std
::
vector
<
int
>&
particleSet
,
float
*
posq
,
std
::
vector
<
OpenMM
::
RealVec
>&
atomCoordinates
,
void
calculateOneIxn
(
std
::
vector
<
int
>&
particleSet
,
RealOpenMM
**
particleParameters
,
std
::
vector
<
OpenMM
::
RealVec
>&
forces
,
RealOpenMM
*
totalEnergy
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
);
RealOpenMM
**
particleParameters
,
float
*
forces
,
ThreadData
&
data
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
);
/**
/**
* Compute the displacement and squared distance between two points, optionally using
* Compute the displacement and squared distance between two points, optionally using
...
@@ -90,9 +101,9 @@ private:
...
@@ -90,9 +101,9 @@ private:
*/
*/
void
getDeltaR
(
const
fvec4
&
posI
,
const
fvec4
&
posJ
,
fvec4
&
deltaR
,
float
&
r2
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
)
const
;
void
getDeltaR
(
const
fvec4
&
posI
,
const
fvec4
&
posJ
,
fvec4
&
deltaR
,
float
&
r2
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
)
const
;
void
computeDelta
(
int
atom1
,
int
atom2
,
RealOpenMM
*
delta
,
std
::
vector
<
OpenMM
::
RealVec
>&
atomCoordinates
)
const
;
void
computeDelta
(
int
atom1
,
int
atom2
,
RealOpenMM
*
delta
,
const
OpenMM
::
RealVec
*
atomCoordinates
)
const
;
static
RealOpenMM
computeAngle
(
RealOpenMM
*
vec1
,
RealOpenMM
*
vec2
);
static
RealOpenMM
computeAngle
(
RealOpenMM
*
vec1
,
RealOpenMM
*
vec2
,
float
sign
);
public:
public:
...
@@ -150,10 +161,7 @@ public:
...
@@ -150,10 +161,7 @@ public:
void
calculateIxn
(
AlignedArray
<
float
>&
posq
,
std
::
vector
<
OpenMM
::
RealVec
>&
atomCoordinates
,
RealOpenMM
**
particleParameters
,
void
calculateIxn
(
AlignedArray
<
float
>&
posq
,
std
::
vector
<
OpenMM
::
RealVec
>&
atomCoordinates
,
RealOpenMM
**
particleParameters
,
const
std
::
map
<
std
::
string
,
double
>&
globalParameters
,
const
std
::
map
<
std
::
string
,
double
>&
globalParameters
,
std
::
vector
<
OpenMM
::
RealVec
>&
forces
,
RealOpenMM
*
totalEnergy
);
std
::
vector
<
AlignedArray
<
float
>
>&
threadForce
,
bool
includeForces
,
bool
includeEnergy
,
double
&
energy
);
// ---------------------------------------------------------------------------------------
};
};
class
CpuCustomManyParticleForce
::
ParticleTermInfo
{
class
CpuCustomManyParticleForce
::
ParticleTermInfo
{
...
@@ -161,10 +169,7 @@ public:
...
@@ -161,10 +169,7 @@ public:
std
::
string
name
;
std
::
string
name
;
int
atom
,
component
,
variableIndex
;
int
atom
,
component
,
variableIndex
;
Lepton
::
CompiledExpression
forceExpression
;
Lepton
::
CompiledExpression
forceExpression
;
ParticleTermInfo
(
const
std
::
string
&
name
,
int
atom
,
int
component
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
CompiledExpressionSet
&
set
)
:
ParticleTermInfo
(
const
std
::
string
&
name
,
int
atom
,
int
component
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
ThreadData
&
data
);
name
(
name
),
atom
(
atom
),
component
(
component
),
forceExpression
(
forceExpression
)
{
variableIndex
=
set
.
getVariableIndex
(
name
);
}
};
};
class
CpuCustomManyParticleForce
::
DistanceTermInfo
{
class
CpuCustomManyParticleForce
::
DistanceTermInfo
{
...
@@ -172,11 +177,9 @@ public:
...
@@ -172,11 +177,9 @@ public:
std
::
string
name
;
std
::
string
name
;
int
p1
,
p2
,
variableIndex
;
int
p1
,
p2
,
variableIndex
;
Lepton
::
CompiledExpression
forceExpression
;
Lepton
::
CompiledExpression
forceExpression
;
mutable
RealOpenMM
delta
[
ReferenceForce
::
LastDeltaRIndex
];
int
delta
;
DistanceTermInfo
(
const
std
::
string
&
name
,
const
std
::
vector
<
int
>&
atoms
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
CompiledExpressionSet
&
set
)
:
float
deltaSign
;
name
(
name
),
p1
(
atoms
[
0
]),
p2
(
atoms
[
1
]),
forceExpression
(
forceExpression
)
{
DistanceTermInfo
(
const
std
::
string
&
name
,
const
std
::
vector
<
int
>&
atoms
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
ThreadData
&
data
);
variableIndex
=
set
.
getVariableIndex
(
name
);
}
};
};
class
CpuCustomManyParticleForce
::
AngleTermInfo
{
class
CpuCustomManyParticleForce
::
AngleTermInfo
{
...
@@ -184,12 +187,9 @@ public:
...
@@ -184,12 +187,9 @@ public:
std
::
string
name
;
std
::
string
name
;
int
p1
,
p2
,
p3
,
variableIndex
;
int
p1
,
p2
,
p3
,
variableIndex
;
Lepton
::
CompiledExpression
forceExpression
;
Lepton
::
CompiledExpression
forceExpression
;
mutable
RealOpenMM
delta1
[
ReferenceForce
::
LastDeltaRIndex
];
int
delta1
,
delta2
;
mutable
RealOpenMM
delta2
[
ReferenceForce
::
LastDeltaRIndex
];
float
delta1Sign
,
delta2Sign
;
AngleTermInfo
(
const
std
::
string
&
name
,
const
std
::
vector
<
int
>&
atoms
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
CompiledExpressionSet
&
set
)
:
AngleTermInfo
(
const
std
::
string
&
name
,
const
std
::
vector
<
int
>&
atoms
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
ThreadData
&
data
);
name
(
name
),
p1
(
atoms
[
0
]),
p2
(
atoms
[
1
]),
p3
(
atoms
[
2
]),
forceExpression
(
forceExpression
)
{
variableIndex
=
set
.
getVariableIndex
(
name
);
}
};
};
class
CpuCustomManyParticleForce
::
DihedralTermInfo
{
class
CpuCustomManyParticleForce
::
DihedralTermInfo
{
...
@@ -197,15 +197,29 @@ public:
...
@@ -197,15 +197,29 @@ public:
std
::
string
name
;
std
::
string
name
;
int
p1
,
p2
,
p3
,
p4
,
variableIndex
;
int
p1
,
p2
,
p3
,
p4
,
variableIndex
;
Lepton
::
CompiledExpression
forceExpression
;
Lepton
::
CompiledExpression
forceExpression
;
mutable
RealOpenMM
delta1
[
ReferenceForce
::
LastDeltaRIndex
];
int
delta1
,
delta2
,
delta3
;
mutable
RealOpenMM
delta2
[
ReferenceForce
::
LastDeltaRIndex
];
mutable
RealOpenMM
delta3
[
ReferenceForce
::
LastDeltaRIndex
];
mutable
RealOpenMM
cross1
[
3
];
mutable
RealOpenMM
cross1
[
3
];
mutable
RealOpenMM
cross2
[
3
];
mutable
RealOpenMM
cross2
[
3
];
DihedralTermInfo
(
const
std
::
string
&
name
,
const
std
::
vector
<
int
>&
atoms
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
CompiledExpressionSet
&
set
)
:
DihedralTermInfo
(
const
std
::
string
&
name
,
const
std
::
vector
<
int
>&
atoms
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
ThreadData
&
data
);
name
(
name
),
p1
(
atoms
[
0
]),
p2
(
atoms
[
1
]),
p3
(
atoms
[
2
]),
p4
(
atoms
[
3
]),
forceExpression
(
forceExpression
)
{
};
variableIndex
=
set
.
getVariableIndex
(
name
);
}
class
CpuCustomManyParticleForce
::
ThreadData
{
public:
CompiledExpressionSet
expressionSet
;
Lepton
::
CompiledExpression
energyExpression
;
std
::
vector
<
std
::
vector
<
int
>
>
particleParamIndices
;
std
::
vector
<
std
::
pair
<
int
,
int
>
>
deltaPairs
;
std
::
vector
<
ParticleTermInfo
>
particleTerms
;
std
::
vector
<
DistanceTermInfo
>
distanceTerms
;
std
::
vector
<
AngleTermInfo
>
angleTerms
;
std
::
vector
<
DihedralTermInfo
>
dihedralTerms
;
double
energy
;
ThreadData
(
const
CustomManyParticleForce
&
force
,
Lepton
::
ParsedExpression
&
energyExpr
,
std
::
map
<
std
::
string
,
std
::
vector
<
int
>
>&
distances
,
std
::
map
<
std
::
string
,
std
::
vector
<
int
>
>&
angles
,
std
::
map
<
std
::
string
,
std
::
vector
<
int
>
>&
dihedrals
);
/**
* Request a pair of particles whose distance or displacement vector is needed in the computation.
*/
void
requestDeltaPair
(
int
p1
,
int
p2
,
int
&
pairIndex
,
float
&
pairSign
,
bool
allowReversed
);
};
};
}
// namespace OpenMM
}
// namespace OpenMM
...
...
platforms/cpu/src/CpuCustomManyParticleForce.cpp
View file @
0e2ffb4b
This diff is collapsed.
Click to expand it.
platforms/cpu/src/CpuKernels.cpp
View file @
0e2ffb4b
...
@@ -870,8 +870,6 @@ void CpuCalcCustomManyParticleForceKernel::initialize(const System& system, cons
...
@@ -870,8 +870,6 @@ void CpuCalcCustomManyParticleForceKernel::initialize(const System& system, cons
double
CpuCalcCustomManyParticleForceKernel
::
execute
(
ContextImpl
&
context
,
bool
includeForces
,
bool
includeEnergy
)
{
double
CpuCalcCustomManyParticleForceKernel
::
execute
(
ContextImpl
&
context
,
bool
includeForces
,
bool
includeEnergy
)
{
vector
<
RealVec
>&
posData
=
extractPositions
(
context
);
vector
<
RealVec
>&
posData
=
extractPositions
(
context
);
vector
<
RealVec
>&
forceData
=
extractForces
(
context
);
RealOpenMM
energy
=
0
;
map
<
string
,
double
>
globalParameters
;
map
<
string
,
double
>
globalParameters
;
for
(
int
i
=
0
;
i
<
(
int
)
globalParameterNames
.
size
();
i
++
)
for
(
int
i
=
0
;
i
<
(
int
)
globalParameterNames
.
size
();
i
++
)
globalParameters
[
globalParameterNames
[
i
]]
=
context
.
getParameter
(
globalParameterNames
[
i
]);
globalParameters
[
globalParameterNames
[
i
]]
=
context
.
getParameter
(
globalParameterNames
[
i
]);
...
@@ -882,7 +880,8 @@ double CpuCalcCustomManyParticleForceKernel::execute(ContextImpl& context, bool
...
@@ -882,7 +880,8 @@ double CpuCalcCustomManyParticleForceKernel::execute(ContextImpl& context, bool
throw
OpenMMException
(
"The periodic box size has decreased to less than twice the nonbonded cutoff."
);
throw
OpenMMException
(
"The periodic box size has decreased to less than twice the nonbonded cutoff."
);
ixn
->
setPeriodic
(
box
);
ixn
->
setPeriodic
(
box
);
}
}
ixn
->
calculateIxn
(
data
.
posq
,
posData
,
particleParamArray
,
globalParameters
,
forceData
,
includeEnergy
?
&
energy
:
NULL
);
double
energy
=
0
;
ixn
->
calculateIxn
(
data
.
posq
,
posData
,
particleParamArray
,
globalParameters
,
data
.
threadForce
,
includeForces
,
includeEnergy
,
energy
);
return
energy
;
return
energy
;
}
}
...
...
platforms/cpu/tests/TestCpuCustomManyParticleForce.cpp
0 → 100644
View file @
0e2ffb4b
This diff is collapsed.
Click to expand it.
platforms/reference/src/SimTKReference/ReferenceCustomManyParticleIxn.cpp
View file @
0e2ffb4b
...
@@ -192,7 +192,7 @@ void ReferenceCustomManyParticleIxn::calculateOneIxn(const vector<int>& particle
...
@@ -192,7 +192,7 @@ void ReferenceCustomManyParticleIxn::calculateOneIxn(const vector<int>& particle
for
(
int
i
=
0
;
i
<
(
int
)
particleTerms
.
size
();
i
++
)
{
for
(
int
i
=
0
;
i
<
(
int
)
particleTerms
.
size
();
i
++
)
{
const
ParticleTermInfo
&
term
=
particleTerms
[
i
];
const
ParticleTermInfo
&
term
=
particleTerms
[
i
];
variables
[
term
.
name
]
=
atomCoordinates
[
term
.
atom
][
term
.
component
];
variables
[
term
.
name
]
=
atomCoordinates
[
permutedParticles
[
term
.
atom
]
]
[
term
.
component
];
}
}
for
(
int
i
=
0
;
i
<
(
int
)
distanceTerms
.
size
();
i
++
)
{
for
(
int
i
=
0
;
i
<
(
int
)
distanceTerms
.
size
();
i
++
)
{
const
DistanceTermInfo
&
term
=
distanceTerms
[
i
];
const
DistanceTermInfo
&
term
=
distanceTerms
[
i
];
...
...
platforms/reference/tests/TestReferenceCustomManyParticleForce.cpp
View file @
0e2ffb4b
...
@@ -39,6 +39,7 @@
...
@@ -39,6 +39,7 @@
#include "openmm/internal/AssertionUtilities.h"
#include "openmm/internal/AssertionUtilities.h"
#include "openmm/Context.h"
#include "openmm/Context.h"
#include "ReferencePlatform.h"
#include "ReferencePlatform.h"
#include "openmm/CustomCompoundBondForce.h"
#include "openmm/CustomManyParticleForce.h"
#include "openmm/CustomManyParticleForce.h"
#include "openmm/System.h"
#include "openmm/System.h"
#include "openmm/TabulatedFunction.h"
#include "openmm/TabulatedFunction.h"
...
@@ -217,6 +218,72 @@ void testExclusions() {
...
@@ -217,6 +218,72 @@ void testExclusions() {
validateAxilrodTeller
(
force
,
positions
,
expectedSets
,
2.0
);
validateAxilrodTeller
(
force
,
positions
,
expectedSets
,
2.0
);
}
}
void
testAllTerms
()
{
int
numParticles
=
4
;
ReferencePlatform
platform
;
// Create a system with a CustomManyParticleForce.
System
system1
;
CustomManyParticleForce
*
force1
=
new
CustomManyParticleForce
(
4
,
"distance(p1,p2)+angle(p1,p4,p3)+dihedral(p1,p3,p2,p4)+x1+y4+z3"
);
system1
.
addForce
(
force1
);
vector
<
double
>
params
;
for
(
int
i
=
0
;
i
<
numParticles
;
i
++
)
{
system1
.
addParticle
(
1.0
);
force1
->
addParticle
(
params
,
i
);
}
set
<
int
>
filter
;
filter
.
insert
(
0
);
force1
->
setTypeFilter
(
0
,
filter
);
filter
.
clear
();
filter
.
insert
(
1
);
force1
->
setTypeFilter
(
1
,
filter
);
filter
.
clear
();
filter
.
insert
(
3
);
force1
->
setTypeFilter
(
2
,
filter
);
filter
.
clear
();
filter
.
insert
(
2
);
force1
->
setTypeFilter
(
3
,
filter
);
// Create a system that use a CustomCompoundBondForce to compute exactly the same interactions.
System
system2
;
CustomCompoundBondForce
*
force2
=
new
CustomCompoundBondForce
(
4
,
"distance(p1,p2)+angle(p1,p3,p4)+dihedral(p1,p4,p2,p3)+x1+y3+z4"
);
system2
.
addForce
(
force2
);
vector
<
int
>
particles
;
particles
.
push_back
(
0
);
particles
.
push_back
(
1
);
particles
.
push_back
(
2
);
particles
.
push_back
(
3
);
force2
->
addBond
(
particles
,
params
);
for
(
int
i
=
0
;
i
<
numParticles
;
i
++
)
system2
.
addParticle
(
1.0
);
// Create contexts for both of them.
vector
<
Vec3
>
positions
;
OpenMM_SFMT
::
SFMT
sfmt
;
init_gen_rand
(
0
,
sfmt
);
for
(
int
i
=
0
;
i
<
numParticles
;
i
++
)
positions
.
push_back
(
Vec3
(
genrand_real2
(
sfmt
),
genrand_real2
(
sfmt
),
genrand_real2
(
sfmt
)));
VerletIntegrator
integrator1
(
0.001
);
VerletIntegrator
integrator2
(
0.001
);
Context
context1
(
system1
,
integrator1
,
platform
);
Context
context2
(
system2
,
integrator2
,
platform
);
context1
.
setPositions
(
positions
);
context2
.
setPositions
(
positions
);
// See if they produce identical forces and energies.
State
state1
=
context1
.
getState
(
State
::
Forces
|
State
::
Energy
);
State
state2
=
context2
.
getState
(
State
::
Forces
|
State
::
Energy
);
ASSERT_EQUAL_TOL
(
state2
.
getPotentialEnergy
(),
state1
.
getPotentialEnergy
(),
1e-4
);
for
(
int
i
=
0
;
i
<
numParticles
;
i
++
)
ASSERT_EQUAL_VEC
(
state2
.
getForces
()[
i
],
state1
.
getForces
()[
i
],
1e-4
);
}
void
testParameters
()
{
void
testParameters
()
{
// Create a system.
// Create a system.
...
@@ -403,6 +470,7 @@ int main() {
...
@@ -403,6 +470,7 @@ int main() {
testCutoff
();
testCutoff
();
testPeriodic
();
testPeriodic
();
testExclusions
();
testExclusions
();
testAllTerms
();
testParameters
();
testParameters
();
testTabulatedFunctions
();
testTabulatedFunctions
();
testTypeFilters
();
testTypeFilters
();
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment