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
b8866444
Commit
b8866444
authored
Mar 17, 2014
by
peastman
Browse files
Finished parallelizing CPU implementation of CustomNonbondedForce
parent
c053a59a
Changes
3
Show whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
91 additions
and
95 deletions
+91
-95
platforms/cpu/include/CpuCustomNonbondedForce.h
platforms/cpu/include/CpuCustomNonbondedForce.h
+56
-64
platforms/cpu/src/CpuCustomNonbondedForce.cpp
platforms/cpu/src/CpuCustomNonbondedForce.cpp
+33
-29
platforms/cpu/src/CpuKernels.cpp
platforms/cpu/src/CpuKernels.cpp
+2
-2
No files found.
platforms/cpu/include/CpuCustomNonbondedForce.h
View file @
b8866444
...
@@ -38,54 +38,6 @@
...
@@ -38,54 +38,6 @@
namespace
OpenMM
{
namespace
OpenMM
{
class
CpuCustomNonbondedForce
{
class
CpuCustomNonbondedForce
{
private:
class
ComputeForceTask
;
class
ThreadData
;
bool
cutoff
;
bool
useSwitch
;
bool
periodic
;
const
CpuNeighborList
*
neighborList
;
RealOpenMM
periodicBoxSize
[
3
];
RealOpenMM
cutoffDistance
,
switchingDistance
;
ThreadPool
&
threads
;
std
::
vector
<
ThreadData
*>
threadData
;
std
::
vector
<
std
::
string
>
paramNames
;
std
::
vector
<
std
::
pair
<
std
::
set
<
int
>
,
std
::
set
<
int
>
>
>
interactionGroups
;
std
::
vector
<
double
>
threadEnergy
;
// The following variables are used to make information accessible to the individual threads.
int
numberOfAtoms
;
float
*
posq
;
RealVec
const
*
atomCoordinates
;
RealOpenMM
**
atomParameters
;
const
std
::
map
<
std
::
string
,
double
>*
globalParameters
;
std
::
set
<
int
>
const
*
exclusions
;
std
::
vector
<
AlignedArray
<
float
>
>*
threadForce
;
bool
includeForce
,
includeEnergy
;
void
*
atomicCounter
;
/**
* This routine contains the code executed by each thread.
*/
void
threadComputeForce
(
ThreadPool
&
threads
,
int
threadIndex
);
/**---------------------------------------------------------------------------------------
Calculate custom pair ixn between two atoms
@param atom1 the index of the first atom
@param atom2 the index of the second atom
@param atomCoordinates atom coordinates
@param atomParameters atomParameters[atomIndex][parameterIndex]
@param forces force array (forces added)
@param totalEnergy total energy
--------------------------------------------------------------------------------------- */
void
calculateOneIxn
(
int
atom1
,
int
atom2
,
ThreadData
&
data
,
float
*
forces
,
double
&
totalEnergy
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
);
public:
public:
/**---------------------------------------------------------------------------------------
/**---------------------------------------------------------------------------------------
...
@@ -95,7 +47,7 @@ class CpuCustomNonbondedForce {
...
@@ -95,7 +47,7 @@ class CpuCustomNonbondedForce {
--------------------------------------------------------------------------------------- */
--------------------------------------------------------------------------------------- */
CpuCustomNonbondedForce
(
const
Lepton
::
CompiledExpression
&
energyExpression
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
CpuCustomNonbondedForce
(
const
Lepton
::
CompiledExpression
&
energyExpression
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
const
std
::
vector
<
std
::
string
>&
parameterNames
,
ThreadPool
&
threads
);
const
std
::
vector
<
std
::
string
>&
parameterNames
,
const
std
::
vector
<
std
::
set
<
int
>
>&
exclusions
,
ThreadPool
&
threads
);
/**---------------------------------------------------------------------------------------
/**---------------------------------------------------------------------------------------
...
@@ -103,7 +55,7 @@ class CpuCustomNonbondedForce {
...
@@ -103,7 +55,7 @@ class CpuCustomNonbondedForce {
--------------------------------------------------------------------------------------- */
--------------------------------------------------------------------------------------- */
~
CpuCustomNonbondedForce
(
);
~
CpuCustomNonbondedForce
();
/**---------------------------------------------------------------------------------------
/**---------------------------------------------------------------------------------------
...
@@ -114,7 +66,7 @@ class CpuCustomNonbondedForce {
...
@@ -114,7 +66,7 @@ class CpuCustomNonbondedForce {
--------------------------------------------------------------------------------------- */
--------------------------------------------------------------------------------------- */
void
setUseCutoff
(
RealOpenMM
distance
,
const
CpuNeighborList
&
neighbors
);
void
setUseCutoff
(
RealOpenMM
distance
,
const
CpuNeighborList
&
neighbors
);
/**---------------------------------------------------------------------------------------
/**---------------------------------------------------------------------------------------
...
@@ -135,7 +87,7 @@ class CpuCustomNonbondedForce {
...
@@ -135,7 +87,7 @@ class CpuCustomNonbondedForce {
--------------------------------------------------------------------------------------- */
--------------------------------------------------------------------------------------- */
void
setUseSwitchingFunction
(
RealOpenMM
distance
);
void
setUseSwitchingFunction
(
RealOpenMM
distance
);
/**---------------------------------------------------------------------------------------
/**---------------------------------------------------------------------------------------
...
@@ -147,7 +99,7 @@ class CpuCustomNonbondedForce {
...
@@ -147,7 +99,7 @@ class CpuCustomNonbondedForce {
--------------------------------------------------------------------------------------- */
--------------------------------------------------------------------------------------- */
void
setPeriodic
(
OpenMM
::
RealVec
&
boxSize
);
void
setPeriodic
(
OpenMM
::
RealVec
&
boxSize
);
/**---------------------------------------------------------------------------------------
/**---------------------------------------------------------------------------------------
...
@@ -157,8 +109,6 @@ class CpuCustomNonbondedForce {
...
@@ -157,8 +109,6 @@ class CpuCustomNonbondedForce {
@param posq atom coordinates in float format
@param posq atom coordinates in float format
@param atomCoordinates atom coordinates
@param atomCoordinates atom coordinates
@param atomParameters atom parameters (charges, c6, c12, ...) atomParameters[atomIndex][paramterIndex]
@param atomParameters atom parameters (charges, c6, c12, ...) atomParameters[atomIndex][paramterIndex]
@param exclusions atom exclusion indices
exclusions[atomIndex] contains the list of exclusions for that atom
@param fixedParameters non atom parameters (not currently used)
@param fixedParameters non atom parameters (not currently used)
@param globalParameters the values of global parameters
@param globalParameters the values of global parameters
@param forces force array (forces added)
@param forces force array (forces added)
...
@@ -167,10 +117,52 @@ class CpuCustomNonbondedForce {
...
@@ -167,10 +117,52 @@ class CpuCustomNonbondedForce {
--------------------------------------------------------------------------------------- */
--------------------------------------------------------------------------------------- */
void
calculatePairIxn
(
int
numberOfAtoms
,
float
*
posq
,
std
::
vector
<
OpenMM
::
RealVec
>&
atomCoordinates
,
void
calculatePairIxn
(
int
numberOfAtoms
,
float
*
posq
,
std
::
vector
<
OpenMM
::
RealVec
>&
atomCoordinates
,
RealOpenMM
**
atomParameters
,
RealOpenMM
**
atomParameters
,
std
::
vector
<
std
::
set
<
int
>
>&
exclusions
,
RealOpenMM
*
fixedParameters
,
const
std
::
map
<
std
::
string
,
double
>&
globalParameters
,
RealOpenMM
*
fixedParameters
,
const
std
::
map
<
std
::
string
,
double
>&
globalParameters
,
std
::
vector
<
AlignedArray
<
float
>
>&
threadForce
,
bool
includeForce
,
bool
includeEnergy
,
double
&
totalEnergy
);
std
::
vector
<
AlignedArray
<
float
>
>&
threadForce
,
bool
includeForce
,
bool
includeEnergy
,
double
&
totalEnergy
);
private:
class
ComputeForceTask
;
class
ThreadData
;
bool
cutoff
;
bool
useSwitch
;
bool
periodic
;
const
CpuNeighborList
*
neighborList
;
RealOpenMM
periodicBoxSize
[
3
];
RealOpenMM
cutoffDistance
,
switchingDistance
;
ThreadPool
&
threads
;
const
std
::
vector
<
std
::
set
<
int
>
>
exclusions
;
std
::
vector
<
ThreadData
*>
threadData
;
std
::
vector
<
std
::
string
>
paramNames
;
std
::
vector
<
std
::
pair
<
int
,
int
>
>
groupInteractions
;
std
::
vector
<
double
>
threadEnergy
;
// The following variables are used to make information accessible to the individual threads.
int
numberOfAtoms
;
float
*
posq
;
RealVec
const
*
atomCoordinates
;
RealOpenMM
**
atomParameters
;
const
std
::
map
<
std
::
string
,
double
>*
globalParameters
;
std
::
vector
<
AlignedArray
<
float
>
>*
threadForce
;
bool
includeForce
,
includeEnergy
;
void
*
atomicCounter
;
/**
* This routine contains the code executed by each thread.
*/
void
threadComputeForce
(
ThreadPool
&
threads
,
int
threadIndex
);
/**
* Calculate the interaction between two atoms.
*
* @param atom1 the index of the first atom
* @param atom2 the index of the second atom
* @param data workspace for the current thread
* @param forces force array (forces added)
* @param totalEnergy total energy
* @param boxSize the size of the periodic box
* @param boxSize the inverse size of the periodic box
*/
void
calculateOneIxn
(
int
atom1
,
int
atom2
,
ThreadData
&
data
,
float
*
forces
,
double
&
totalEnergy
,
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
...
...
platforms/cpu/src/CpuCustomNonbondedForce.cpp
View file @
b8866444
...
@@ -60,8 +60,8 @@ CpuCustomNonbondedForce::ThreadData::ThreadData(const Lepton::CompiledExpression
...
@@ -60,8 +60,8 @@ CpuCustomNonbondedForce::ThreadData::ThreadData(const Lepton::CompiledExpression
}
}
CpuCustomNonbondedForce
::
CpuCustomNonbondedForce
(
const
Lepton
::
CompiledExpression
&
energyExpression
,
CpuCustomNonbondedForce
::
CpuCustomNonbondedForce
(
const
Lepton
::
CompiledExpression
&
energyExpression
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
const
vector
<
string
>&
parameterNames
,
ThreadPool
&
threads
)
:
const
Lepton
::
CompiledExpression
&
forceExpression
,
const
vector
<
string
>&
parameterNames
,
const
vector
<
set
<
int
>
>&
exclusions
,
ThreadPool
&
threads
)
:
cutoff
(
false
),
useSwitch
(
false
),
periodic
(
false
),
paramNames
(
parameterNames
),
threads
(
threads
)
{
cutoff
(
false
),
useSwitch
(
false
),
periodic
(
false
),
paramNames
(
parameterNames
),
exclusions
(
exclusions
),
threads
(
threads
)
{
for
(
int
i
=
0
;
i
<
threads
.
getNumThreads
();
i
++
)
for
(
int
i
=
0
;
i
<
threads
.
getNumThreads
();
i
++
)
threadData
.
push_back
(
new
ThreadData
(
energyExpression
,
forceExpression
,
parameterNames
));
threadData
.
push_back
(
new
ThreadData
(
energyExpression
,
forceExpression
,
parameterNames
));
}
}
...
@@ -78,7 +78,19 @@ void CpuCustomNonbondedForce::setUseCutoff(RealOpenMM distance, const CpuNeighbo
...
@@ -78,7 +78,19 @@ void CpuCustomNonbondedForce::setUseCutoff(RealOpenMM distance, const CpuNeighbo
}
}
void
CpuCustomNonbondedForce
::
setInteractionGroups
(
const
vector
<
pair
<
set
<
int
>
,
set
<
int
>
>
>&
groups
)
{
void
CpuCustomNonbondedForce
::
setInteractionGroups
(
const
vector
<
pair
<
set
<
int
>
,
set
<
int
>
>
>&
groups
)
{
interactionGroups
=
groups
;
for
(
int
group
=
0
;
group
<
(
int
)
groups
.
size
();
group
++
)
{
const
set
<
int
>&
set1
=
groups
[
group
].
first
;
const
set
<
int
>&
set2
=
groups
[
group
].
second
;
for
(
set
<
int
>::
const_iterator
atom1
=
set1
.
begin
();
atom1
!=
set1
.
end
();
++
atom1
)
{
for
(
set
<
int
>::
const_iterator
atom2
=
set2
.
begin
();
atom2
!=
set2
.
end
();
++
atom2
)
{
if
(
*
atom1
==
*
atom2
||
exclusions
[
*
atom1
].
find
(
*
atom2
)
!=
exclusions
[
*
atom1
].
end
())
continue
;
// This is an excluded interaction.
if
(
*
atom1
>
*
atom2
&&
set1
.
find
(
*
atom2
)
!=
set1
.
end
()
&&
set2
.
find
(
*
atom1
)
!=
set2
.
end
())
continue
;
// Both atoms are in both sets, so skip duplicate interactions.
groupInteractions
.
push_back
(
make_pair
(
*
atom1
,
*
atom2
));
}
}
}
}
}
void
CpuCustomNonbondedForce
::
setUseSwitchingFunction
(
RealOpenMM
distance
)
{
void
CpuCustomNonbondedForce
::
setUseSwitchingFunction
(
RealOpenMM
distance
)
{
...
@@ -98,8 +110,7 @@ void CpuCustomNonbondedForce::setPeriodic(RealVec& boxSize) {
...
@@ -98,8 +110,7 @@ void CpuCustomNonbondedForce::setPeriodic(RealVec& boxSize) {
}
}
void
CpuCustomNonbondedForce
::
calculatePairIxn
(
int
numberOfAtoms
,
float
*
posq
,
vector
<
RealVec
>&
atomCoordinates
,
void
CpuCustomNonbondedForce
::
calculatePairIxn
(
int
numberOfAtoms
,
float
*
posq
,
vector
<
RealVec
>&
atomCoordinates
,
RealOpenMM
**
atomParameters
,
RealOpenMM
**
atomParameters
,
vector
<
set
<
int
>
>&
exclusions
,
RealOpenMM
*
fixedParameters
,
const
map
<
string
,
double
>&
globalParameters
,
RealOpenMM
*
fixedParameters
,
const
map
<
string
,
double
>&
globalParameters
,
vector
<
AlignedArray
<
float
>
>&
threadForce
,
bool
includeForce
,
bool
includeEnergy
,
double
&
totalEnergy
)
{
vector
<
AlignedArray
<
float
>
>&
threadForce
,
bool
includeForce
,
bool
includeEnergy
,
double
&
totalEnergy
)
{
// Record the parameters for the threads.
// Record the parameters for the threads.
...
@@ -109,7 +120,6 @@ void CpuCustomNonbondedForce::calculatePairIxn(int numberOfAtoms, float* posq, v
...
@@ -109,7 +120,6 @@ void CpuCustomNonbondedForce::calculatePairIxn(int numberOfAtoms, float* posq, v
this
->
atomCoordinates
=
&
atomCoordinates
[
0
];
this
->
atomCoordinates
=
&
atomCoordinates
[
0
];
this
->
atomParameters
=
atomParameters
;
this
->
atomParameters
=
atomParameters
;
this
->
globalParameters
=
&
globalParameters
;
this
->
globalParameters
=
&
globalParameters
;
this
->
exclusions
=
&
exclusions
[
0
];
this
->
threadForce
=
&
threadForce
;
this
->
threadForce
=
&
threadForce
;
this
->
includeForce
=
includeForce
;
this
->
includeForce
=
includeForce
;
this
->
includeEnergy
=
includeEnergy
;
this
->
includeEnergy
=
includeEnergy
;
...
@@ -147,29 +157,22 @@ void CpuCustomNonbondedForce::threadComputeForce(ThreadPool& threads, int thread
...
@@ -147,29 +157,22 @@ void CpuCustomNonbondedForce::threadComputeForce(ThreadPool& threads, int thread
}
}
fvec4
boxSize
(
periodicBoxSize
[
0
],
periodicBoxSize
[
1
],
periodicBoxSize
[
2
],
0
);
fvec4
boxSize
(
periodicBoxSize
[
0
],
periodicBoxSize
[
1
],
periodicBoxSize
[
2
],
0
);
fvec4
invBoxSize
((
1
/
periodicBoxSize
[
0
]),
(
1
/
periodicBoxSize
[
1
]),
(
1
/
periodicBoxSize
[
2
]),
0
);
fvec4
invBoxSize
((
1
/
periodicBoxSize
[
0
]),
(
1
/
periodicBoxSize
[
1
]),
(
1
/
periodicBoxSize
[
2
]),
0
);
if
(
interactionGroups
.
size
()
>
0
)
{
if
(
groupInteractions
.
size
()
>
0
)
{
if
(
threadIndex
>
0
)
return
;
// The user has specified interaction groups, so compute only the requested interactions.
// The user has specified interaction groups, so compute only the requested interactions.
for
(
int
group
=
0
;
group
<
(
int
)
interactionGroups
.
size
();
group
++
)
{
while
(
true
)
{
const
set
<
int
>&
set1
=
interactionGroups
[
group
].
first
;
int
i
=
gmx_atomic_fetch_add
(
reinterpret_cast
<
gmx_atomic_t
*>
(
atomicCounter
),
1
);
const
set
<
int
>&
set2
=
interactionGroups
[
group
].
second
;
if
(
i
>=
groupInteractions
.
size
())
for
(
set
<
int
>::
const_iterator
atom1
=
set1
.
begin
();
atom1
!=
set1
.
end
();
++
atom1
)
{
break
;
for
(
set
<
int
>::
const_iterator
atom2
=
set2
.
begin
();
atom2
!=
set2
.
end
();
++
atom2
)
{
int
atom1
=
groupInteractions
[
i
].
first
;
if
(
*
atom1
==
*
atom2
||
exclusions
[
*
atom1
].
find
(
*
atom2
)
!=
exclusions
[
*
atom1
].
end
())
int
atom2
=
groupInteractions
[
i
].
second
;
continue
;
// This is an excluded interaction.
if
(
*
atom1
>
*
atom2
&&
set1
.
find
(
*
atom2
)
!=
set1
.
end
()
&&
set2
.
find
(
*
atom1
)
!=
set2
.
end
())
continue
;
// Both atoms are in both sets, so skip duplicate interactions.
for
(
int
j
=
0
;
j
<
(
int
)
paramNames
.
size
();
j
++
)
{
for
(
int
j
=
0
;
j
<
(
int
)
paramNames
.
size
();
j
++
)
{
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
],
atomParameters
[
*
atom1
][
j
]);
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
],
atomParameters
[
atom1
][
j
]);
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
+
1
],
atomParameters
[
*
atom2
][
j
]);
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
+
1
],
atomParameters
[
atom2
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
],
atomParameters
[
*
atom1
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
],
atomParameters
[
atom1
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
+
1
],
atomParameters
[
*
atom2
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
+
1
],
atomParameters
[
atom2
][
j
]);
}
calculateOneIxn
(
*
atom1
,
*
atom2
,
data
,
forces
,
energy
,
boxSize
,
invBoxSize
);
}
}
}
calculateOneIxn
(
atom1
,
atom2
,
data
,
forces
,
energy
,
boxSize
,
invBoxSize
);
}
}
}
}
else
if
(
cutoff
)
{
else
if
(
cutoff
)
{
...
@@ -206,9 +209,10 @@ void CpuCustomNonbondedForce::threadComputeForce(ThreadPool& threads, int thread
...
@@ -206,9 +209,10 @@ void CpuCustomNonbondedForce::threadComputeForce(ThreadPool& threads, int thread
else
{
else
{
// Every particle interacts with every other one.
// Every particle interacts with every other one.
if
(
threadIndex
>
0
)
while
(
true
)
{
return
;
int
ii
=
gmx_atomic_fetch_add
(
reinterpret_cast
<
gmx_atomic_t
*>
(
atomicCounter
),
1
);
for
(
int
ii
=
0
;
ii
<
numberOfAtoms
;
ii
++
)
{
if
(
ii
>=
numberOfAtoms
)
break
;
for
(
int
jj
=
ii
+
1
;
jj
<
numberOfAtoms
;
jj
++
)
{
for
(
int
jj
=
ii
+
1
;
jj
<
numberOfAtoms
;
jj
++
)
{
if
(
exclusions
[
jj
].
find
(
ii
)
==
exclusions
[
jj
].
end
())
{
if
(
exclusions
[
jj
].
find
(
ii
)
==
exclusions
[
jj
].
end
())
{
for
(
int
j
=
0
;
j
<
(
int
)
paramNames
.
size
();
j
++
)
{
for
(
int
j
=
0
;
j
<
(
int
)
paramNames
.
size
();
j
++
)
{
...
...
platforms/cpu/src/CpuKernels.cpp
View file @
b8866444
...
@@ -719,7 +719,7 @@ void CpuCalcCustomNonbondedForceKernel::initialize(const System& system, const C
...
@@ -719,7 +719,7 @@ void CpuCalcCustomNonbondedForceKernel::initialize(const System& system, const C
interactionGroups
.
push_back
(
make_pair
(
set1
,
set2
));
interactionGroups
.
push_back
(
make_pair
(
set1
,
set2
));
}
}
data
.
isPeriodic
=
(
nonbondedMethod
==
CutoffPeriodic
);
data
.
isPeriodic
=
(
nonbondedMethod
==
CutoffPeriodic
);
nonbonded
=
new
CpuCustomNonbondedForce
(
energyExpression
,
forceExpression
,
parameterNames
,
data
.
threads
);
nonbonded
=
new
CpuCustomNonbondedForce
(
energyExpression
,
forceExpression
,
parameterNames
,
exclusions
,
data
.
threads
);
}
}
double
CpuCalcCustomNonbondedForceKernel
::
execute
(
ContextImpl
&
context
,
bool
includeForces
,
bool
includeEnergy
)
{
double
CpuCalcCustomNonbondedForceKernel
::
execute
(
ContextImpl
&
context
,
bool
includeForces
,
bool
includeEnergy
)
{
...
@@ -750,7 +750,7 @@ double CpuCalcCustomNonbondedForceKernel::execute(ContextImpl& context, bool inc
...
@@ -750,7 +750,7 @@ double CpuCalcCustomNonbondedForceKernel::execute(ContextImpl& context, bool inc
}
}
if
(
useSwitchingFunction
)
if
(
useSwitchingFunction
)
nonbonded
->
setUseSwitchingFunction
(
switchingDistance
);
nonbonded
->
setUseSwitchingFunction
(
switchingDistance
);
nonbonded
->
calculatePairIxn
(
numParticles
,
&
data
.
posq
[
0
],
posData
,
particleParamArray
,
exclusions
,
0
,
globalParamValues
,
data
.
threadForce
,
includeForces
,
includeEnergy
,
energy
);
nonbonded
->
calculatePairIxn
(
numParticles
,
&
data
.
posq
[
0
],
posData
,
particleParamArray
,
0
,
globalParamValues
,
data
.
threadForce
,
includeForces
,
includeEnergy
,
energy
);
// Add in the long range correction.
// Add in the long range correction.
...
...
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