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
c053a59a
Commit
c053a59a
authored
Mar 17, 2014
by
peastman
Browse files
Began parallelizing CPU implementation of CustomNonbondedForce
parent
7ab954bb
Changes
4
Show whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
129 additions
and
170 deletions
+129
-170
platforms/cpu/include/CpuCustomNonbondedForce.h
platforms/cpu/include/CpuCustomNonbondedForce.h
+25
-10
platforms/cpu/include/CpuKernels.h
platforms/cpu/include/CpuKernels.h
+2
-5
platforms/cpu/src/CpuCustomNonbondedForce.cpp
platforms/cpu/src/CpuCustomNonbondedForce.cpp
+86
-146
platforms/cpu/src/CpuKernels.cpp
platforms/cpu/src/CpuKernels.cpp
+16
-9
No files found.
platforms/cpu/include/CpuCustomNonbondedForce.h
View file @
c053a59a
...
@@ -40,6 +40,8 @@ namespace OpenMM {
...
@@ -40,6 +40,8 @@ namespace OpenMM {
class
CpuCustomNonbondedForce
{
class
CpuCustomNonbondedForce
{
private:
private:
class
ComputeForceTask
;
class
ThreadData
;
bool
cutoff
;
bool
cutoff
;
bool
useSwitch
;
bool
useSwitch
;
...
@@ -47,13 +49,9 @@ class CpuCustomNonbondedForce {
...
@@ -47,13 +49,9 @@ class CpuCustomNonbondedForce {
const
CpuNeighborList
*
neighborList
;
const
CpuNeighborList
*
neighborList
;
RealOpenMM
periodicBoxSize
[
3
];
RealOpenMM
periodicBoxSize
[
3
];
RealOpenMM
cutoffDistance
,
switchingDistance
;
RealOpenMM
cutoffDistance
,
switchingDistance
;
Lepton
::
CompiledExpression
energyExpression
;
ThreadPool
&
threads
;
Lepton
::
CompiledExpression
forceExpression
;
std
::
vector
<
ThreadData
*>
threadData
;
std
::
vector
<
std
::
string
>
paramNames
;
std
::
vector
<
std
::
string
>
paramNames
;
std
::
vector
<
double
*>
energyParticleParams
;
std
::
vector
<
double
*>
forceParticleParams
;
double
*
energyR
;
double
*
forceR
;
std
::
vector
<
std
::
pair
<
std
::
set
<
int
>
,
std
::
set
<
int
>
>
>
interactionGroups
;
std
::
vector
<
std
::
pair
<
std
::
set
<
int
>
,
std
::
set
<
int
>
>
>
interactionGroups
;
std
::
vector
<
double
>
threadEnergy
;
std
::
vector
<
double
>
threadEnergy
;
// The following variables are used to make information accessible to the individual threads.
// The following variables are used to make information accessible to the individual threads.
...
@@ -61,11 +59,17 @@ class CpuCustomNonbondedForce {
...
@@ -61,11 +59,17 @@ class CpuCustomNonbondedForce {
float
*
posq
;
float
*
posq
;
RealVec
const
*
atomCoordinates
;
RealVec
const
*
atomCoordinates
;
RealOpenMM
**
atomParameters
;
RealOpenMM
**
atomParameters
;
const
std
::
map
<
std
::
string
,
double
>*
globalParameters
;
std
::
set
<
int
>
const
*
exclusions
;
std
::
set
<
int
>
const
*
exclusions
;
std
::
vector
<
AlignedArray
<
float
>
>*
threadForce
;
std
::
vector
<
AlignedArray
<
float
>
>*
threadForce
;
bool
includeEnergy
;
bool
includeForce
,
includeEnergy
;
void
*
atomicCounter
;
void
*
atomicCounter
;
/**
* This routine contains the code executed by each thread.
*/
void
threadComputeForce
(
ThreadPool
&
threads
,
int
threadIndex
);
/**---------------------------------------------------------------------------------------
/**---------------------------------------------------------------------------------------
Calculate custom pair ixn between two atoms
Calculate custom pair ixn between two atoms
...
@@ -79,7 +83,7 @@ class CpuCustomNonbondedForce {
...
@@ -79,7 +83,7 @@ class CpuCustomNonbondedForce {
--------------------------------------------------------------------------------------- */
--------------------------------------------------------------------------------------- */
void
calculateOneIxn
(
int
atom1
,
int
atom2
,
float
*
forces
,
RealOpenMM
*
totalEnergy
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
);
void
calculateOneIxn
(
int
atom1
,
int
atom2
,
ThreadData
&
data
,
float
*
forces
,
double
&
totalEnergy
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
);
public:
public:
...
@@ -91,7 +95,7 @@ class CpuCustomNonbondedForce {
...
@@ -91,7 +95,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
);
const
std
::
vector
<
std
::
string
>&
parameterNames
,
ThreadPool
&
threads
);
/**---------------------------------------------------------------------------------------
/**---------------------------------------------------------------------------------------
...
@@ -166,7 +170,7 @@ class CpuCustomNonbondedForce {
...
@@ -166,7 +170,7 @@ 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
,
std
::
vector
<
std
::
set
<
int
>
>&
exclusions
,
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
,
double
*
totalEnergy
,
ThreadPool
&
threads
);
std
::
vector
<
AlignedArray
<
float
>
>&
threadForce
,
bool
includeForce
,
bool
includeEnergy
,
double
&
totalEnergy
);
/**
/**
* Compute the displacement and squared distance between two points, optionally using
* Compute the displacement and squared distance between two points, optionally using
...
@@ -175,6 +179,17 @@ class CpuCustomNonbondedForce {
...
@@ -175,6 +179,17 @@ class CpuCustomNonbondedForce {
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
;
};
};
class
CpuCustomNonbondedForce
::
ThreadData
{
public:
ThreadData
(
const
Lepton
::
CompiledExpression
&
energyExpression
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
const
std
::
vector
<
std
::
string
>&
parameterNames
);
Lepton
::
CompiledExpression
energyExpression
;
Lepton
::
CompiledExpression
forceExpression
;
std
::
vector
<
double
*>
energyParticleParams
;
std
::
vector
<
double
*>
forceParticleParams
;
double
*
energyR
;
double
*
forceR
;
};
}
// namespace OpenMM
}
// namespace OpenMM
#endif // OPENMM_CPU_CUSTOM_NONBONDED_FORCE_H__
#endif // OPENMM_CPU_CUSTOM_NONBONDED_FORCE_H__
platforms/cpu/include/CpuKernels.h
View file @
c053a59a
...
@@ -41,7 +41,6 @@
...
@@ -41,7 +41,6 @@
#include "CpuPlatform.h"
#include "CpuPlatform.h"
#include "openmm/kernels.h"
#include "openmm/kernels.h"
#include "openmm/System.h"
#include "openmm/System.h"
#include "lepton/CompiledExpression.h"
namespace
OpenMM
{
namespace
OpenMM
{
...
@@ -224,9 +223,7 @@ private:
...
@@ -224,9 +223,7 @@ private:
*/
*/
class
CpuCalcCustomNonbondedForceKernel
:
public
CalcCustomNonbondedForceKernel
{
class
CpuCalcCustomNonbondedForceKernel
:
public
CalcCustomNonbondedForceKernel
{
public:
public:
CpuCalcCustomNonbondedForceKernel
(
std
::
string
name
,
const
Platform
&
platform
,
CpuPlatform
::
PlatformData
&
data
)
:
CalcCustomNonbondedForceKernel
(
name
,
platform
),
CpuCalcCustomNonbondedForceKernel
(
std
::
string
name
,
const
Platform
&
platform
,
CpuPlatform
::
PlatformData
&
data
);
data
(
data
),
forceCopy
(
NULL
),
neighborList
(
NULL
)
{
}
~
CpuCalcCustomNonbondedForceKernel
();
~
CpuCalcCustomNonbondedForceKernel
();
/**
/**
* Initialize the kernel.
* Initialize the kernel.
...
@@ -260,11 +257,11 @@ private:
...
@@ -260,11 +257,11 @@ private:
CustomNonbondedForce
*
forceCopy
;
CustomNonbondedForce
*
forceCopy
;
std
::
map
<
std
::
string
,
double
>
globalParamValues
;
std
::
map
<
std
::
string
,
double
>
globalParamValues
;
std
::
vector
<
std
::
set
<
int
>
>
exclusions
;
std
::
vector
<
std
::
set
<
int
>
>
exclusions
;
Lepton
::
CompiledExpression
energyExpression
,
forceExpression
;
std
::
vector
<
std
::
string
>
parameterNames
,
globalParameterNames
;
std
::
vector
<
std
::
string
>
parameterNames
,
globalParameterNames
;
std
::
vector
<
std
::
pair
<
std
::
set
<
int
>
,
std
::
set
<
int
>
>
>
interactionGroups
;
std
::
vector
<
std
::
pair
<
std
::
set
<
int
>
,
std
::
set
<
int
>
>
>
interactionGroups
;
NonbondedMethod
nonbondedMethod
;
NonbondedMethod
nonbondedMethod
;
CpuNeighborList
*
neighborList
;
CpuNeighborList
*
neighborList
;
CpuCustomNonbondedForce
*
nonbonded
;
};
};
/**
/**
...
...
platforms/cpu/src/CpuCustomNonbondedForce.cpp
View file @
c053a59a
...
@@ -35,104 +35,58 @@
...
@@ -35,104 +35,58 @@
using
namespace
OpenMM
;
using
namespace
OpenMM
;
using
namespace
std
;
using
namespace
std
;
/**---------------------------------------------------------------------------------------
class
CpuCustomNonbondedForce
::
ComputeForceTask
:
public
ThreadPool
::
Task
{
public:
CpuCustomNonbondedForce constructor
ComputeForceTask
(
CpuCustomNonbondedForce
&
owner
)
:
owner
(
owner
)
{
}
--------------------------------------------------------------------------------------- */
void
execute
(
ThreadPool
&
threads
,
int
threadIndex
)
{
owner
.
threadComputeForce
(
threads
,
threadIndex
);
CpuCustomNonbondedForce
::
CpuCustomNonbondedForce
(
const
Lepton
::
CompiledExpression
&
energyExpression
,
}
const
Lepton
::
CompiledExpression
&
forceExpression
,
const
vector
<
string
>&
parameterNames
)
:
CpuCustomNonbondedForce
&
owner
;
cutoff
(
false
),
useSwitch
(
false
),
periodic
(
false
),
energyExpression
(
energyExpression
),
forceExpression
(
forceExpression
),
paramNames
(
parameterNames
)
{
};
// ---------------------------------------------------------------------------------------
// static const char* methodName = "\nCpuCustomNonbondedForce::CpuCustomNonbondedForce";
// ---------------------------------------------------------------------------------------
CpuCustomNonbondedForce
::
ThreadData
::
ThreadData
(
const
Lepton
::
CompiledExpression
&
energyExpression
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
const
vector
<
string
>&
parameterNames
)
:
energyExpression
(
energyExpression
),
forceExpression
(
forceExpression
)
{
energyR
=
ReferenceForce
::
getVariablePointer
(
this
->
energyExpression
,
"r"
);
energyR
=
ReferenceForce
::
getVariablePointer
(
this
->
energyExpression
,
"r"
);
forceR
=
ReferenceForce
::
getVariablePointer
(
this
->
forceExpression
,
"r"
);
forceR
=
ReferenceForce
::
getVariablePointer
(
this
->
forceExpression
,
"r"
);
for
(
int
i
=
0
;
i
<
(
int
)
paramNames
.
size
();
i
++
)
{
for
(
int
i
=
0
;
i
<
(
int
)
param
eter
Names
.
size
();
i
++
)
{
for
(
int
j
=
1
;
j
<
3
;
j
++
)
{
for
(
int
j
=
1
;
j
<
3
;
j
++
)
{
stringstream
name
;
stringstream
name
;
name
<<
paramNames
[
i
]
<<
j
;
name
<<
param
eter
Names
[
i
]
<<
j
;
energyParticleParams
.
push_back
(
ReferenceForce
::
getVariablePointer
(
this
->
energyExpression
,
name
.
str
()));
energyParticleParams
.
push_back
(
ReferenceForce
::
getVariablePointer
(
this
->
energyExpression
,
name
.
str
()));
forceParticleParams
.
push_back
(
ReferenceForce
::
getVariablePointer
(
this
->
forceExpression
,
name
.
str
()));
forceParticleParams
.
push_back
(
ReferenceForce
::
getVariablePointer
(
this
->
forceExpression
,
name
.
str
()));
}
}
}
}
}
}
/**---------------------------------------------------------------------------------------
CpuCustomNonbondedForce
::
CpuCustomNonbondedForce
(
const
Lepton
::
CompiledExpression
&
energyExpression
,
const
Lepton
::
CompiledExpression
&
forceExpression
,
const
vector
<
string
>&
parameterNames
,
ThreadPool
&
threads
)
:
CpuCustomNonbondedForce destructor
cutoff
(
false
),
useSwitch
(
false
),
periodic
(
false
),
paramNames
(
parameterNames
),
threads
(
threads
)
{
for
(
int
i
=
0
;
i
<
threads
.
getNumThreads
();
i
++
)
--------------------------------------------------------------------------------------- */
threadData
.
push_back
(
new
ThreadData
(
energyExpression
,
forceExpression
,
parameterNames
));
CpuCustomNonbondedForce
::~
CpuCustomNonbondedForce
(
){
// ---------------------------------------------------------------------------------------
// static const char* methodName = "\nCpuCustomNonbondedForce::~CpuCustomNonbondedForce";
// ---------------------------------------------------------------------------------------
}
}
/**---------------------------------------------------------------------------------------
CpuCustomNonbondedForce
::~
CpuCustomNonbondedForce
()
{
for
(
int
i
=
0
;
i
<
(
int
)
threadData
.
size
();
i
++
)
Set the force to use a cutoff.
delete
threadData
[
i
];
}
@param distance the cutoff distance
@param neighbors the neighbor list to use
--------------------------------------------------------------------------------------- */
void
CpuCustomNonbondedForce
::
setUseCutoff
(
RealOpenMM
distance
,
const
CpuNeighborList
&
neighbors
)
{
void
CpuCustomNonbondedForce
::
setUseCutoff
(
RealOpenMM
distance
,
const
CpuNeighborList
&
neighbors
)
{
cutoff
=
true
;
cutoff
=
true
;
cutoffDistance
=
distance
;
cutoffDistance
=
distance
;
neighborList
=
&
neighbors
;
neighborList
=
&
neighbors
;
}
}
/**---------------------------------------------------------------------------------------
Restrict the force to a list of interaction groups.
@param distance the cutoff distance
@param neighbors the neighbor list to use
--------------------------------------------------------------------------------------- */
void
CpuCustomNonbondedForce
::
setInteractionGroups
(
const
vector
<
pair
<
set
<
int
>
,
set
<
int
>
>
>&
groups
)
{
void
CpuCustomNonbondedForce
::
setInteractionGroups
(
const
vector
<
pair
<
set
<
int
>
,
set
<
int
>
>
>&
groups
)
{
interactionGroups
=
groups
;
interactionGroups
=
groups
;
}
}
/**---------------------------------------------------------------------------------------
void
CpuCustomNonbondedForce
::
setUseSwitchingFunction
(
RealOpenMM
distance
)
{
Set the force to use a switching function.
@param distance the switching distance
--------------------------------------------------------------------------------------- */
void
CpuCustomNonbondedForce
::
setUseSwitchingFunction
(
RealOpenMM
distance
)
{
useSwitch
=
true
;
useSwitch
=
true
;
switchingDistance
=
distance
;
switchingDistance
=
distance
;
}
}
/**---------------------------------------------------------------------------------------
void
CpuCustomNonbondedForce
::
setPeriodic
(
RealVec
&
boxSize
)
{
Set the force to use periodic boundary conditions. This requires that a cutoff has
also been set, and the smallest side of the periodic box is at least twice the cutoff
distance.
@param boxSize the X, Y, and Z widths of the periodic box
--------------------------------------------------------------------------------------- */
void
CpuCustomNonbondedForce
::
setPeriodic
(
RealVec
&
boxSize
)
{
assert
(
cutoff
);
assert
(
cutoff
);
assert
(
boxSize
[
0
]
>=
2.0
*
cutoffDistance
);
assert
(
boxSize
[
0
]
>=
2.0
*
cutoffDistance
);
assert
(
boxSize
[
1
]
>=
2.0
*
cutoffDistance
);
assert
(
boxSize
[
1
]
>=
2.0
*
cutoffDistance
);
...
@@ -141,39 +95,61 @@ void CpuCustomNonbondedForce::setUseSwitchingFunction( RealOpenMM distance ) {
...
@@ -141,39 +95,61 @@ void CpuCustomNonbondedForce::setUseSwitchingFunction( RealOpenMM distance ) {
periodicBoxSize
[
0
]
=
boxSize
[
0
];
periodicBoxSize
[
0
]
=
boxSize
[
0
];
periodicBoxSize
[
1
]
=
boxSize
[
1
];
periodicBoxSize
[
1
]
=
boxSize
[
1
];
periodicBoxSize
[
2
]
=
boxSize
[
2
];
periodicBoxSize
[
2
]
=
boxSize
[
2
];
}
}
void
CpuCustomNonbondedForce
::
calculatePairIxn
(
int
numberOfAtoms
,
float
*
posq
,
vector
<
RealVec
>&
atomCoordinates
,
void
CpuCustomNonbondedForce
::
calculatePairIxn
(
int
numberOfAtoms
,
float
*
posq
,
vector
<
RealVec
>&
atomCoordinates
,
RealOpenMM
**
atomParameters
,
vector
<
set
<
int
>
>&
exclusions
,
RealOpenMM
**
atomParameters
,
vector
<
set
<
int
>
>&
exclusions
,
RealOpenMM
*
fixedParameters
,
const
map
<
string
,
double
>&
globalParameters
,
RealOpenMM
*
fixedParameters
,
const
map
<
string
,
double
>&
globalParameters
,
vector
<
AlignedArray
<
float
>
>&
threadForce
,
double
*
totalEnergy
,
ThreadPool
&
threads
)
{
vector
<
AlignedArray
<
float
>
>&
threadForce
,
bool
includeForce
,
bool
includeEnergy
,
double
&
totalEnergy
)
{
// Record the parameters for the threads.
// Record the parameters for the threads.
this
->
numberOfAtoms
=
numberOfAtoms
;
this
->
numberOfAtoms
=
numberOfAtoms
;
this
->
posq
=
posq
;
this
->
posq
=
posq
;
this
->
atomCoordinates
=
&
atomCoordinates
[
0
];
this
->
atomCoordinates
=
&
atomCoordinates
[
0
];
this
->
atomParameters
=
atomParameters
;
this
->
atomParameters
=
atomParameters
;
this
->
globalParameters
=
&
globalParameters
;
this
->
exclusions
=
&
exclusions
[
0
];
this
->
exclusions
=
&
exclusions
[
0
];
this
->
threadForce
=
&
threadForce
;
this
->
threadForce
=
&
threadForce
;
includeEnergy
=
(
totalEnergy
!=
NULL
);
this
->
includeForce
=
includeForce
;
this
->
includeEnergy
=
includeEnergy
;
threadEnergy
.
resize
(
threads
.
getNumThreads
());
threadEnergy
.
resize
(
threads
.
getNumThreads
());
gmx_atomic_t
counter
;
gmx_atomic_t
counter
;
gmx_atomic_set
(
&
counter
,
0
);
gmx_atomic_set
(
&
counter
,
0
);
this
->
atomicCounter
=
&
counter
;
this
->
atomicCounter
=
&
counter
;
// Signal the threads to start running and wait for them to finish.
float
*
forces
=
&
threadForce
[
0
][
0
];
ComputeForceTask
task
(
*
this
);
threads
.
execute
(
task
);
threads
.
waitForThreads
();
// Combine the energies from all the threads.
if
(
includeEnergy
)
{
int
numThreads
=
threads
.
getNumThreads
();
for
(
int
i
=
0
;
i
<
numThreads
;
i
++
)
totalEnergy
+=
threadEnergy
[
i
];
}
}
for
(
map
<
string
,
double
>::
const_iterator
iter
=
globalParameters
.
begin
();
iter
!=
globalParameters
.
end
();
++
iter
)
{
void
CpuCustomNonbondedForce
::
threadComputeForce
(
ThreadPool
&
threads
,
int
threadIndex
)
{
ReferenceForce
::
setVariable
(
ReferenceForce
::
getVariablePointer
(
energyExpression
,
iter
->
first
),
iter
->
second
);
// Compute this thread's subset of interactions.
ReferenceForce
::
setVariable
(
ReferenceForce
::
getVariablePointer
(
forceExpression
,
iter
->
first
),
iter
->
second
);
int
numThreads
=
threads
.
getNumThreads
();
threadEnergy
[
threadIndex
]
=
0
;
double
&
energy
=
threadEnergy
[
threadIndex
];
float
*
forces
=
&
(
*
threadForce
)[
threadIndex
][
0
];
ThreadData
&
data
=
*
threadData
[
threadIndex
];
for
(
map
<
string
,
double
>::
const_iterator
iter
=
globalParameters
->
begin
();
iter
!=
globalParameters
->
end
();
++
iter
)
{
ReferenceForce
::
setVariable
(
ReferenceForce
::
getVariablePointer
(
data
.
energyExpression
,
iter
->
first
),
iter
->
second
);
ReferenceForce
::
setVariable
(
ReferenceForce
::
getVariablePointer
(
data
.
forceExpression
,
iter
->
first
),
iter
->
second
);
}
}
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
(
interactionGroups
.
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
++
)
{
for
(
int
group
=
0
;
group
<
(
int
)
interactionGroups
.
size
();
group
++
)
{
...
@@ -186,12 +162,12 @@ void CpuCustomNonbondedForce::calculatePairIxn(int numberOfAtoms, float* posq, v
...
@@ -186,12 +162,12 @@ void CpuCustomNonbondedForce::calculatePairIxn(int numberOfAtoms, float* posq, v
if
(
*
atom1
>
*
atom2
&&
set1
.
find
(
*
atom2
)
!=
set1
.
end
()
&&
set2
.
find
(
*
atom1
)
!=
set2
.
end
())
if
(
*
atom1
>
*
atom2
&&
set1
.
find
(
*
atom2
)
!=
set1
.
end
()
&&
set2
.
find
(
*
atom1
)
!=
set2
.
end
())
continue
;
// Both atoms are in both sets, so skip duplicate interactions.
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
(
energyParticleParams
[
j
*
2
],
atomParameters
[
*
atom1
][
j
]);
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
],
atomParameters
[
*
atom1
][
j
]);
ReferenceForce
::
setVariable
(
energyParticleParams
[
j
*
2
+
1
],
atomParameters
[
*
atom2
][
j
]);
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
+
1
],
atomParameters
[
*
atom2
][
j
]);
ReferenceForce
::
setVariable
(
forceParticleParams
[
j
*
2
],
atomParameters
[
*
atom1
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
],
atomParameters
[
*
atom1
][
j
]);
ReferenceForce
::
setVariable
(
forceParticleParams
[
j
*
2
+
1
],
atomParameters
[
*
atom2
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
+
1
],
atomParameters
[
*
atom2
][
j
]);
}
}
calculateOneIxn
(
*
atom1
,
*
atom2
,
forces
,
totalE
nergy
,
boxSize
,
invBoxSize
);
calculateOneIxn
(
*
atom1
,
*
atom2
,
data
,
forces
,
e
nergy
,
boxSize
,
invBoxSize
);
}
}
}
}
}
}
...
@@ -199,7 +175,10 @@ void CpuCustomNonbondedForce::calculatePairIxn(int numberOfAtoms, float* posq, v
...
@@ -199,7 +175,10 @@ void CpuCustomNonbondedForce::calculatePairIxn(int numberOfAtoms, float* posq, v
else
if
(
cutoff
)
{
else
if
(
cutoff
)
{
// We are using a cutoff, so get the interactions from the neighbor list.
// We are using a cutoff, so get the interactions from the neighbor list.
for
(
int
blockIndex
=
0
;
blockIndex
<
neighborList
->
getNumBlocks
();
blockIndex
++
)
{
while
(
true
)
{
int
blockIndex
=
gmx_atomic_fetch_add
(
reinterpret_cast
<
gmx_atomic_t
*>
(
atomicCounter
),
1
);
if
(
blockIndex
>=
neighborList
->
getNumBlocks
())
break
;
int
blockAtom
[
4
];
int
blockAtom
[
4
];
for
(
int
i
=
0
;
i
<
4
;
i
++
)
for
(
int
i
=
0
;
i
<
4
;
i
++
)
blockAtom
[
i
]
=
neighborList
->
getSortedAtoms
()[
4
*
blockIndex
+
i
];
blockAtom
[
i
]
=
neighborList
->
getSortedAtoms
()[
4
*
blockIndex
+
i
];
...
@@ -208,84 +187,46 @@ void CpuCustomNonbondedForce::calculatePairIxn(int numberOfAtoms, float* posq, v
...
@@ -208,84 +187,46 @@ void CpuCustomNonbondedForce::calculatePairIxn(int numberOfAtoms, float* posq, v
for
(
int
i
=
0
;
i
<
(
int
)
neighbors
.
size
();
i
++
)
{
for
(
int
i
=
0
;
i
<
(
int
)
neighbors
.
size
();
i
++
)
{
int
first
=
neighbors
[
i
];
int
first
=
neighbors
[
i
];
for
(
int
j
=
0
;
j
<
(
int
)
paramNames
.
size
();
j
++
)
{
for
(
int
j
=
0
;
j
<
(
int
)
paramNames
.
size
();
j
++
)
{
ReferenceForce
::
setVariable
(
energyParticleParams
[
j
*
2
],
atomParameters
[
first
][
j
]);
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
],
atomParameters
[
first
][
j
]);
ReferenceForce
::
setVariable
(
forceParticleParams
[
j
*
2
],
atomParameters
[
first
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
],
atomParameters
[
first
][
j
]);
}
}
for
(
int
k
=
0
;
k
<
4
;
k
++
)
{
for
(
int
k
=
0
;
k
<
4
;
k
++
)
{
if
((
exclusions
[
i
]
&
(
1
<<
k
))
==
0
)
{
if
((
exclusions
[
i
]
&
(
1
<<
k
))
==
0
)
{
int
second
=
blockAtom
[
k
];
int
second
=
blockAtom
[
k
];
for
(
int
j
=
0
;
j
<
(
int
)
paramNames
.
size
();
j
++
)
{
for
(
int
j
=
0
;
j
<
(
int
)
paramNames
.
size
();
j
++
)
{
ReferenceForce
::
setVariable
(
energyParticleParams
[
j
*
2
+
1
],
atomParameters
[
second
][
j
]);
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
+
1
],
atomParameters
[
second
][
j
]);
ReferenceForce
::
setVariable
(
forceParticleParams
[
j
*
2
+
1
],
atomParameters
[
second
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
+
1
],
atomParameters
[
second
][
j
]);
}
}
calculateOneIxn
(
first
,
second
,
forces
,
totalE
nergy
,
boxSize
,
invBoxSize
);
calculateOneIxn
(
first
,
second
,
data
,
forces
,
e
nergy
,
boxSize
,
invBoxSize
);
}
}
}
}
}
}
}
}
// for (int i = 0; i < (int) neighborList->size(); i++) {
// OpenMM::AtomPair pair = (*neighborList)[i];
// for (int j = 0; j < (int) paramNames.size(); j++) {
// ReferenceForce::setVariable(energyParticleParams[j*2], atomParameters[pair.first][j]);
// ReferenceForce::setVariable(energyParticleParams[j*2+1], atomParameters[pair.second][j]);
// ReferenceForce::setVariable(forceParticleParams[j*2], atomParameters[pair.first][j]);
// ReferenceForce::setVariable(forceParticleParams[j*2+1], atomParameters[pair.second][j]);
// }
// calculateOneIxn(pair.first, pair.second, atomCoordinates, forces, energyByAtom, totalEnergy);
// }
}
}
else
{
else
{
// Every particle interacts with every other one.
// Every particle interacts with every other one.
if
(
threadIndex
>
0
)
return
;
for
(
int
ii
=
0
;
ii
<
numberOfAtoms
;
ii
++
)
{
for
(
int
ii
=
0
;
ii
<
numberOfAtoms
;
ii
++
)
{
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
++
)
{
ReferenceForce
::
setVariable
(
energyParticleParams
[
j
*
2
],
atomParameters
[
ii
][
j
]);
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
],
atomParameters
[
ii
][
j
]);
ReferenceForce
::
setVariable
(
energyParticleParams
[
j
*
2
+
1
],
atomParameters
[
jj
][
j
]);
ReferenceForce
::
setVariable
(
data
.
energyParticleParams
[
j
*
2
+
1
],
atomParameters
[
jj
][
j
]);
ReferenceForce
::
setVariable
(
forceParticleParams
[
j
*
2
],
atomParameters
[
ii
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
],
atomParameters
[
ii
][
j
]);
ReferenceForce
::
setVariable
(
forceParticleParams
[
j
*
2
+
1
],
atomParameters
[
jj
][
j
]);
ReferenceForce
::
setVariable
(
data
.
forceParticleParams
[
j
*
2
+
1
],
atomParameters
[
jj
][
j
]);
}
}
calculateOneIxn
(
ii
,
jj
,
forces
,
totalE
nergy
,
boxSize
,
invBoxSize
);
calculateOneIxn
(
ii
,
jj
,
data
,
forces
,
e
nergy
,
boxSize
,
invBoxSize
);
}
}
}
}
}
}
}
}
}
}
/**---------------------------------------------------------------------------------------
void
CpuCustomNonbondedForce
::
calculateOneIxn
(
int
ii
,
int
jj
,
ThreadData
&
data
,
float
*
forces
,
double
&
totalEnergy
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
)
{
Calculate one pair ixn between two atoms
// Get deltaR, R2, and R between 2 atoms
@param ii the index of the first atom
@param jj the index of the second atom
@param atomCoordinates atom coordinates
@param atomParameters atom parameters (charges, c6, c12, ...) atomParameters[atomIndex][paramterIndex]
@param forces force array (forces added)
@param totalEnergy total energy
--------------------------------------------------------------------------------------- */
void
CpuCustomNonbondedForce
::
calculateOneIxn
(
int
ii
,
int
jj
,
float
*
forces
,
RealOpenMM
*
totalEnergy
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
)
{
// ---------------------------------------------------------------------------------------
static
const
std
::
string
methodName
=
"
\n
CpuCustomNonbondedForce::calculateOneIxn"
;
// ---------------------------------------------------------------------------------------
// constants -- reduce Visual Studio warnings regarding conversions between float & double
static
const
RealOpenMM
zero
=
0.0
;
static
const
RealOpenMM
one
=
1.0
;
static
const
RealOpenMM
two
=
2.0
;
static
const
RealOpenMM
three
=
3.0
;
static
const
RealOpenMM
six
=
6.0
;
static
const
RealOpenMM
twelve
=
12.0
;
static
const
RealOpenMM
oneM
=
-
1.0
;
// get deltaR, R2, and R between 2 atoms
fvec4
deltaR
;
fvec4
deltaR
;
fvec4
posI
(
posq
+
4
*
ii
);
fvec4
posI
(
posq
+
4
*
ii
);
...
@@ -298,10 +239,10 @@ void CpuCustomNonbondedForce::calculateOneIxn(int ii, int jj, float* forces, Rea
...
@@ -298,10 +239,10 @@ void CpuCustomNonbondedForce::calculateOneIxn(int ii, int jj, float* forces, Rea
// accumulate forces
// accumulate forces
ReferenceForce
::
setVariable
(
energyR
,
r
);
ReferenceForce
::
setVariable
(
data
.
energyR
,
r
);
ReferenceForce
::
setVariable
(
forceR
,
r
);
ReferenceForce
::
setVariable
(
data
.
forceR
,
r
);
double
dEdR
=
forceExpression
.
evaluate
()
/
r
;
double
dEdR
=
(
includeForce
?
data
.
forceExpression
.
evaluate
()
/
r
:
0.0
)
;
double
energy
=
energyExpression
.
evaluate
();
double
energy
=
(
includeEnergy
?
data
.
energyExpression
.
evaluate
()
:
0.0
)
;
if
(
useSwitch
)
{
if
(
useSwitch
)
{
if
(
r
>
switchingDistance
)
{
if
(
r
>
switchingDistance
)
{
RealOpenMM
t
=
(
r
-
switchingDistance
)
/
(
cutoffDistance
-
switchingDistance
);
RealOpenMM
t
=
(
r
-
switchingDistance
)
/
(
cutoffDistance
-
switchingDistance
);
...
@@ -317,8 +258,7 @@ void CpuCustomNonbondedForce::calculateOneIxn(int ii, int jj, float* forces, Rea
...
@@ -317,8 +258,7 @@ void CpuCustomNonbondedForce::calculateOneIxn(int ii, int jj, float* forces, Rea
// accumulate energies
// accumulate energies
if
(
totalEnergy
)
totalEnergy
+=
energy
;
*
totalEnergy
+=
energy
;
}
}
void
CpuCustomNonbondedForce
::
getDeltaR
(
const
fvec4
&
posI
,
const
fvec4
&
posJ
,
fvec4
&
deltaR
,
float
&
r2
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
)
const
{
void
CpuCustomNonbondedForce
::
getDeltaR
(
const
fvec4
&
posI
,
const
fvec4
&
posJ
,
fvec4
&
deltaR
,
float
&
r2
,
const
fvec4
&
boxSize
,
const
fvec4
&
invBoxSize
)
const
{
...
...
platforms/cpu/src/CpuKernels.cpp
View file @
c053a59a
...
@@ -6,7 +6,7 @@
...
@@ -6,7 +6,7 @@
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Biological Structures at Stanford, funded under the NIH Roadmap for *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* Medical Research, grant U54 GM072970. See https://simtk.org. *
* *
* *
* Portions copyright (c) 2013 Stanford University and the Authors.
*
* Portions copyright (c) 2013
-2014
Stanford University and the Authors. *
* Authors: Peter Eastman *
* Authors: Peter Eastman *
* Contributors: *
* Contributors: *
* *
* *
...
@@ -45,6 +45,7 @@
...
@@ -45,6 +45,7 @@
#include "openmm/internal/NonbondedForceImpl.h"
#include "openmm/internal/NonbondedForceImpl.h"
#include "openmm/internal/vectorize.h"
#include "openmm/internal/vectorize.h"
#include "RealVec.h"
#include "RealVec.h"
#include "lepton/CompiledExpression.h"
#include "lepton/CustomFunction.h"
#include "lepton/CustomFunction.h"
#include "lepton/Parser.h"
#include "lepton/Parser.h"
#include "lepton/ParsedExpression.h"
#include "lepton/ParsedExpression.h"
...
@@ -623,6 +624,10 @@ void CpuCalcNonbondedForceKernel::copyParametersToContext(ContextImpl& context,
...
@@ -623,6 +624,10 @@ void CpuCalcNonbondedForceKernel::copyParametersToContext(ContextImpl& context,
dispersionCoefficient
=
NonbondedForceImpl
::
calcDispersionCorrection
(
context
.
getSystem
(),
force
);
dispersionCoefficient
=
NonbondedForceImpl
::
calcDispersionCorrection
(
context
.
getSystem
(),
force
);
}
}
CpuCalcCustomNonbondedForceKernel
::
CpuCalcCustomNonbondedForceKernel
(
string
name
,
const
Platform
&
platform
,
CpuPlatform
::
PlatformData
&
data
)
:
CalcCustomNonbondedForceKernel
(
name
,
platform
),
data
(
data
),
forceCopy
(
NULL
),
neighborList
(
NULL
),
nonbonded
(
NULL
)
{
}
CpuCalcCustomNonbondedForceKernel
::~
CpuCalcCustomNonbondedForceKernel
()
{
CpuCalcCustomNonbondedForceKernel
::~
CpuCalcCustomNonbondedForceKernel
()
{
if
(
particleParamArray
!=
NULL
)
{
if
(
particleParamArray
!=
NULL
)
{
for
(
int
i
=
0
;
i
<
numParticles
;
i
++
)
for
(
int
i
=
0
;
i
<
numParticles
;
i
++
)
...
@@ -631,6 +636,8 @@ CpuCalcCustomNonbondedForceKernel::~CpuCalcCustomNonbondedForceKernel() {
...
@@ -631,6 +636,8 @@ CpuCalcCustomNonbondedForceKernel::~CpuCalcCustomNonbondedForceKernel() {
}
}
if
(
neighborList
!=
NULL
)
if
(
neighborList
!=
NULL
)
delete
neighborList
;
delete
neighborList
;
if
(
nonbonded
!=
NULL
)
delete
nonbonded
;
if
(
forceCopy
!=
NULL
)
if
(
forceCopy
!=
NULL
)
delete
forceCopy
;
delete
forceCopy
;
}
}
...
@@ -679,8 +686,8 @@ void CpuCalcCustomNonbondedForceKernel::initialize(const System& system, const C
...
@@ -679,8 +686,8 @@ void CpuCalcCustomNonbondedForceKernel::initialize(const System& system, const C
// Parse the various expressions used to calculate the force.
// Parse the various expressions used to calculate the force.
Lepton
::
ParsedExpression
expression
=
Lepton
::
Parser
::
parse
(
force
.
getEnergyFunction
(),
functions
).
optimize
();
Lepton
::
ParsedExpression
expression
=
Lepton
::
Parser
::
parse
(
force
.
getEnergyFunction
(),
functions
).
optimize
();
energyExpression
=
expression
.
createCompiledExpression
();
Lepton
::
CompiledExpression
energyExpression
=
expression
.
createCompiledExpression
();
forceExpression
=
expression
.
differentiate
(
"r"
).
createCompiledExpression
();
Lepton
::
CompiledExpression
forceExpression
=
expression
.
differentiate
(
"r"
).
createCompiledExpression
();
for
(
int
i
=
0
;
i
<
numParameters
;
i
++
)
for
(
int
i
=
0
;
i
<
numParameters
;
i
++
)
parameterNames
.
push_back
(
force
.
getPerParticleParameterName
(
i
));
parameterNames
.
push_back
(
force
.
getPerParticleParameterName
(
i
));
for
(
int
i
=
0
;
i
<
force
.
getNumGlobalParameters
();
i
++
)
{
for
(
int
i
=
0
;
i
<
force
.
getNumGlobalParameters
();
i
++
)
{
...
@@ -712,6 +719,7 @@ void CpuCalcCustomNonbondedForceKernel::initialize(const System& system, const C
...
@@ -712,6 +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
);
}
}
double
CpuCalcCustomNonbondedForceKernel
::
execute
(
ContextImpl
&
context
,
bool
includeForces
,
bool
includeEnergy
)
{
double
CpuCalcCustomNonbondedForceKernel
::
execute
(
ContextImpl
&
context
,
bool
includeForces
,
bool
includeEnergy
)
{
...
@@ -720,20 +728,19 @@ double CpuCalcCustomNonbondedForceKernel::execute(ContextImpl& context, bool inc
...
@@ -720,20 +728,19 @@ double CpuCalcCustomNonbondedForceKernel::execute(ContextImpl& context, bool inc
RealVec
&
box
=
extractBoxSize
(
context
);
RealVec
&
box
=
extractBoxSize
(
context
);
float
floatBoxSize
[
3
]
=
{(
float
)
box
[
0
],
(
float
)
box
[
1
],
(
float
)
box
[
2
]};
float
floatBoxSize
[
3
]
=
{(
float
)
box
[
0
],
(
float
)
box
[
1
],
(
float
)
box
[
2
]};
double
energy
=
0
;
double
energy
=
0
;
CpuCustomNonbondedForce
ixn
(
energyExpression
,
forceExpression
,
parameterNames
);
bool
periodic
=
(
nonbondedMethod
==
CutoffPeriodic
);
bool
periodic
=
(
nonbondedMethod
==
CutoffPeriodic
);
if
(
nonbondedMethod
!=
NoCutoff
)
{
if
(
nonbondedMethod
!=
NoCutoff
)
{
neighborList
->
computeNeighborList
(
numParticles
,
data
.
posq
,
exclusions
,
floatBoxSize
,
data
.
isPeriodic
,
nonbondedCutoff
,
data
.
threads
);
neighborList
->
computeNeighborList
(
numParticles
,
data
.
posq
,
exclusions
,
floatBoxSize
,
data
.
isPeriodic
,
nonbondedCutoff
,
data
.
threads
);
ixn
.
setUseCutoff
(
nonbondedCutoff
,
*
neighborList
);
nonbonded
->
setUseCutoff
(
nonbondedCutoff
,
*
neighborList
);
}
}
if
(
periodic
)
{
if
(
periodic
)
{
double
minAllowedSize
=
2
*
nonbondedCutoff
;
double
minAllowedSize
=
2
*
nonbondedCutoff
;
if
(
box
[
0
]
<
minAllowedSize
||
box
[
1
]
<
minAllowedSize
||
box
[
2
]
<
minAllowedSize
)
if
(
box
[
0
]
<
minAllowedSize
||
box
[
1
]
<
minAllowedSize
||
box
[
2
]
<
minAllowedSize
)
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
);
nonbonded
->
setPeriodic
(
box
);
}
}
if
(
interactionGroups
.
size
()
>
0
)
if
(
interactionGroups
.
size
()
>
0
)
ixn
.
setInteractionGroups
(
interactionGroups
);
nonbonded
->
setInteractionGroups
(
interactionGroups
);
bool
globalParamsChanged
=
false
;
bool
globalParamsChanged
=
false
;
for
(
int
i
=
0
;
i
<
(
int
)
globalParameterNames
.
size
();
i
++
)
{
for
(
int
i
=
0
;
i
<
(
int
)
globalParameterNames
.
size
();
i
++
)
{
double
value
=
context
.
getParameter
(
globalParameterNames
[
i
]);
double
value
=
context
.
getParameter
(
globalParameterNames
[
i
]);
...
@@ -742,8 +749,8 @@ double CpuCalcCustomNonbondedForceKernel::execute(ContextImpl& context, bool inc
...
@@ -742,8 +749,8 @@ double CpuCalcCustomNonbondedForceKernel::execute(ContextImpl& context, bool inc
globalParamValues
[
globalParameterNames
[
i
]]
=
value
;
globalParamValues
[
globalParameterNames
[
i
]]
=
value
;
}
}
if
(
useSwitchingFunction
)
if
(
useSwitchingFunction
)
ixn
.
setUseSwitchingFunction
(
switchingDistance
);
nonbonded
->
setUseSwitchingFunction
(
switchingDistance
);
ixn
.
calculatePairIxn
(
numParticles
,
&
data
.
posq
[
0
],
posData
,
particleParamArray
,
exclusions
,
0
,
globalParamValues
,
data
.
threadForce
,
includeEnergy
?
&
energy
:
NULL
,
data
.
threads
);
nonbonded
->
calculatePairIxn
(
numParticles
,
&
data
.
posq
[
0
],
posData
,
particleParamArray
,
exclusions
,
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