diff --git a/applications/utilities/displacementField/Make/files b/applications/utilities/displacementField/Make/files
new file mode 100644
index 00000000..816a220c
--- /dev/null
+++ b/applications/utilities/displacementField/Make/files
@@ -0,0 +1,3 @@
+displacementField.C
+
+EXE = $(CFDEM_APP_DIR)/displacementField
diff --git a/applications/utilities/displacementField/Make/options b/applications/utilities/displacementField/Make/options
new file mode 100644
index 00000000..d27c95d0
--- /dev/null
+++ b/applications/utilities/displacementField/Make/options
@@ -0,0 +1,7 @@
+EXE_INC = \
+ -I$(LIB_SRC)/finiteVolume/lnInclude \
+ -I$(LIB_SRC)/meshTools/lnInclude
+
+EXE_LIBS = \
+ -lfiniteVolume \
+ -lmeshTools
diff --git a/applications/utilities/displacementField/displacementField.C b/applications/utilities/displacementField/displacementField.C
new file mode 100644
index 00000000..307fcac4
--- /dev/null
+++ b/applications/utilities/displacementField/displacementField.C
@@ -0,0 +1,393 @@
+/*---------------------------------------------------------------------------*\
+ ========= |
+ \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
+ \\ / O peration | Website: https://openfoam.org
+ \\ / A nd | Copyright (C) 2011-2018 OpenFOAM Foundation
+ \\/ M anipulation |
+-------------------------------------------------------------------------------
+License
+ This file is part of OpenFOAM.
+
+ OpenFOAM is free software: you can redistribute it and/or modify it
+ under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
+ ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with OpenFOAM. If not, see .
+
+Application
+ displacementField
+
+\*---------------------------------------------------------------------------*/
+
+#include "fvCFD.H"
+#include "vectorList.H"
+#include
+#include
+#include
+#include
+#include
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
+void findPairs(labelList &, labelList &, labelPairList &);
+void findPairsUnordered(labelList &, labelList &, labelPairList &);
+void interpolateCellValues(fvMesh &, label , labelList &, volVectorField &, volVectorField &, scalarList &, scalar);
+void nearestNeighborCells(fvMesh &, label, label, labelList &, labelList &);
+void readDump(std::string, labelList &, vectorList &);
+scalar weightFun(scalar);
+
+int main(int argc, char *argv[])
+{
+ argList::addOption
+ (
+ "totalProcs",
+ "label",
+ "total number of parallel processes, defaults to 1"
+ );
+ argList::addOption
+ (
+ "thisProc",
+ "label",
+ "number of current process, defaults to 0"
+ );
+
+
+ #include "setRootCase.H"
+ #include "createTime.H"
+ #include "createMesh.H"
+
+ const label thisProc = args.optionLookupOrDefault("thisProc", 0);
+ const label totalProcs = args.optionLookupOrDefault("totalProcs", 1);
+
+ Info << "This is number " << thisProc << " of " << totalProcs << " processes." << endl;
+
+
+ // user-defined input for each case
+ IOdictionary displacementProperties
+ (
+ IOobject
+ (
+ "displacementProperties",
+ mesh.time().constant(),
+ mesh,
+ IOobject::MUST_READ,
+ IOobject::NO_WRITE
+ )
+ );
+
+ label dumpIndexStart(readLabel(displacementProperties.lookup("dumpIndexStart")));
+ label dumpIndexIncrement(readLabel(displacementProperties.lookup("dumpIndexIncrement")));
+ label nNeighMin(readLabel(displacementProperties.lookup("nNeighMin")));
+ scalar timePerStep(readScalar(displacementProperties.lookup("timePerStep")));
+ scalar startTime(readScalar(displacementProperties.lookup("startTime")));
+ std::string filepath=string(displacementProperties.lookup("filepath"));
+ std::string fileext=string(displacementProperties.lookupOrDefault("fileextension",""));
+ bool fillEmptyCells=bool(displacementProperties.lookupOrDefault("fillEmptyCells",true));
+ scalar xmin=scalar(displacementProperties.lookupOrDefault("xmin",-1e10));
+ scalar xmax=scalar(displacementProperties.lookupOrDefault("xmax",1e10));
+ scalar ymin=scalar(displacementProperties.lookupOrDefault("ymin",-1e10));
+ scalar ymax=scalar(displacementProperties.lookupOrDefault("ymax",1e10));
+ scalar zmin=scalar(displacementProperties.lookupOrDefault("zmin",-1e10));
+ scalar zmax=scalar(displacementProperties.lookupOrDefault("zmax",1e10));
+ scalarList boundaries(6);
+ boundaries[0]=xmin;
+ boundaries[1]=xmax;
+ boundaries[2]=ymin;
+ boundaries[3]=ymax;
+ boundaries[4]=zmin;
+ boundaries[5]=zmax;
+
+
+ label dumpIndex1 = dumpIndexStart + thisProc * dumpIndexIncrement;
+ label dumpIndex2 = dumpIndex1 + dumpIndexIncrement;
+
+ volVectorField Us
+ (
+ IOobject
+ (
+ "UDisp",
+ runTime.timeName(),
+ mesh,
+ IOobject::NO_READ,
+ IOobject::NO_WRITE
+ ),
+ mesh,
+ dimensionedVector("zero", dimensionSet(0,1,-1,0,0), vector::zero)
+ );
+
+ volVectorField UsDirectedVariance
+ (
+ IOobject
+ (
+ "UDispDirectedVariance",
+ runTime.timeName(),
+ mesh,
+ IOobject::NO_READ,
+ IOobject::NO_WRITE
+ ),
+ mesh,
+ dimensionedVector("zero", dimensionSet(0,1,-1,0,0), vector::zero)
+ );
+
+ scalar currTime=startTime + thisProc * timePerStep;
+ label timeIndex=thisProc;
+
+ while(true)
+ {
+ runTime.setTime(currTime,timeIndex);
+ // read dump files and check which particle indices are present in both
+ labelList indices1, indices2;
+ vectorList positions1, positions2;
+
+ std::stringstream ss;
+ ss << filepath << dumpIndex1 << fileext;
+ std::string filename1 = ss.str();
+ ss.str("");
+ ss << filepath << dumpIndex2 << fileext;
+ std::string filename2 = ss.str();
+
+ if (access( filename1.c_str(), F_OK ) == -1 || access( filename2.c_str(), F_OK ) == -1) break;
+
+ Info << "\nReading" << endl;
+ Info << "\t" << filename1 << endl;
+ Info << "\t" << filename2 << endl;
+ Info << "corresponding to time = " << currTime << "." << endl;
+
+ readDump(filename1, indices1, positions1);
+ readDump(filename2, indices2, positions2);
+
+ labelPairList pairs;
+ findPairs(indices1,indices2,pairs);
+
+ // average particle displacements and their variance
+ Info << "Binning particle displacements on mesh." << endl;
+ labelList particlesInCell(mesh.nCells(), 0);
+ vector position, displacement;
+ label line1, line2;
+ label cellI;
+
+ Us *= 0.0;
+ UsDirectedVariance *= 0.0;
+ for (label partI = 0; partI < pairs.size(); partI++)
+ {
+ line1 = pairs[partI].first();
+ line2 = pairs[partI].second();
+ position = positions1[line1];
+ displacement = positions2[line2] - positions1[line1];
+ cellI = mesh.findCell(position);
+ if (cellI < 0) continue;
+ particlesInCell[cellI] += 1;
+ Us[cellI] += displacement;
+
+ for (label comp=0;comp<3;comp++)
+ {
+ UsDirectedVariance[cellI].component(comp) += displacement.component(comp)*displacement.component(comp);
+ }
+ }
+
+
+ for (label cellJ = 0; cellJ 0)
+ {
+ Us[cellJ] /= particlesInCell[cellJ];
+ UsDirectedVariance[cellJ] /= particlesInCell[cellJ];
+ for (label comp=0;comp<3;comp++)
+ {
+ UsDirectedVariance[cellJ].component(comp) -= Us[cellJ].component(comp)*Us[cellJ].component(comp);
+ if (UsDirectedVariance[cellJ].component(comp) > 0) UsDirectedVariance[cellJ].component(comp) = Foam::sqrt(UsDirectedVariance[cellJ].component(comp));
+ }
+ }
+ }
+
+
+ // interpolate values for empty cells
+ if (fillEmptyCells)
+ {
+ Info << "Interpolating empty cells." << endl;
+ interpolateCellValues(mesh,nNeighMin,particlesInCell,Us,UsDirectedVariance,boundaries,timePerStep);
+ }
+
+// dumpIndex1 = dumpIndex2;
+// dumpIndex2 = dumpIndex1 + dumpIndexIncrement;
+ dumpIndex1 += dumpIndexIncrement*totalProcs;
+ dumpIndex2 += dumpIndexIncrement*totalProcs;
+
+ Us /= timePerStep;
+ UsDirectedVariance /= timePerStep;
+ Us.write();
+ UsDirectedVariance.write();
+ currTime += timePerStep*totalProcs;
+ timeIndex += totalProcs;
+ }
+ return 0;
+}
+
+void readDump(std::string filename, labelList &indices, vectorList &positions)
+{
+ #include
+
+ const label leadingLines = 9;
+ label lineCounter = 0;
+ label partIndex;
+ scalar x, y, z;
+
+ indices.clear();
+ positions.clear();
+
+ std::ifstream file(filename);
+ std::string str;
+ while (std::getline(file, str))
+ {
+ if (lineCounter >= leadingLines)
+ {
+ sscanf(str.c_str(), "%d %lf %lf %lf", &partIndex, &x, &y, &z);
+ indices.append(partIndex);
+ positions.append(vector(x,y,z));
+ }
+ lineCounter++;
+ }
+}
+
+void findPairs(labelList &indices1, labelList &indices2, labelPairList &pairs)
+{
+ // remove all entries from first list if they are not present in second list
+ // this assumes ordered entries
+
+ if (indices2.size() == 0) return;
+
+ for (label i=0;i index1) j2 = jmid;
+ else if (indices2[jmid] < index1) j1 = jmid;
+ else
+ {
+ pairs.append(labelPair(i,jmid));
+ break;
+ }
+ if (j2-j1 == 1) break;
+ }
+ }
+ Info << "findPairs: " << pairs.size() << " pairs found." << endl;
+}
+
+void findPairsUnordered(labelList &indices1, labelList &indices2, labelPairList &pairs)
+{
+ // remove all entries from first list if they are not present in second list
+
+ for (label i=0;i 0) continue;
+
+ vector position = mesh.C()[cellI];
+ if (position.x() < boundaries[0] || position.x() > boundaries[1]) continue;
+ if (position.y() < boundaries[2] || position.y() > boundaries[3]) continue;
+ if (position.z() < boundaries[4] || position.z() > boundaries[5]) continue;
+
+ nearestNeighborCells(mesh, cellI, nNeighMin, particlesInCell, neighborsWithValues);
+ weightSum = 0.0;
+ weights.clear();
+ for (label neighI=0; neighI neighbors;
+ std::set