OpenMPCD
FlowProfile.py
1 class FlowProfile:
2  """
3  Represents a flow profile, as created by `OpenMPCD::FlowProfile`.
4  """
5 
6  def __init__(self, rundir):
7  """
8  The constructor.
9  """
10 
12 
13  self._lastRun = None
14 
15  self._points = {}
16  self._outputBlocks = []
17 
18  self.addRun(rundir)
19 
20 
21  def addRun(self, rundir):
22  """
23  Adds the flow profile data in the given `rundir` to the data gathered so
24  far.
25  """
26 
27  if self._outputBlocks:
28  raise Exception("Cannot currently concatenate two multi-block runs")
29 
30 
31  from .Run import Run
32  self._lastRun = Run(rundir)
33 
34 
36 
37  import os.path
38 
39  if os.path.isfile(rundir + '/flowProfile.data.bz2'):
40  import bz2
41  tmpfile = bz2.BZ2File(rundir + '/flowProfile.data.bz2')
42  elif os.path.isfile(rundir + '/flowProfile.data'):
43  tmpfile = open(rundir + '/flowProfile.data', 'r')
44  else:
45  raise Exception("Invalid rundir: " + rundir)
46 
47  for line in tmpfile:
48  if line[0] == '#':
49  continue
50 
51  if line == "\n":
52  self._outputBlocks.append(self._points)
53  self._points = {}
54  continue
55 
56  columns = line.split()
57  slice_ = [ int(i) for i in columns[0:3] ]
58  mean = [ float(i) for i in columns[3:6] ]
59  standardDeviation = [ float(i) for i in columns [6:9] ]
60  sampleSize = int(columns[9])
61 
62  self._addSampleToPoint(slice_, mean, standardDeviation, sampleSize)
63 
64  if self._outputBlocks:
65  self._outputBlocks.append(self._points)
66  self._points = {}
67 
68 
70  self, standardError = 1, theory = True, shift = True):
71  """
72  Returns a `matplotlib.axes.Axes` object that contains a plot of the
73  flow profile, with the horizontal axis showing the simulation `y`
74  coordinate, and the vertical axis showing the average flow velocity in
75  `x` direction (along with its standard deviation) at that point.
76 
77  @param[in] standardError
78  Include a shaded region around the mean that corresponds to
79  `standardError` times the standard error. Set to `0` to omit
80  this shaded region altogether.
81  @param[in] theory
82  Set to `True` to also include a plot of the theoretical shear
83  flow profile.
84  @param[in] shift
85  Set to `True` to shift the data points along the `y`
86  direction in such a way that a data point lies not at the
87  beginning of the `y` segment it describes, but rather at its
88  center.
89  """
90 
91  import matplotlib.figure
92 
93  figure = matplotlib.figure.Figure()
94  axes = figure.add_subplot(1, 1, 1)
95  lines = []
96  legendLabels = []
97 
98  velocityIndex = 0
99 
100  data = self.getFlowProfileAsFunctionOfY(shift)
101  values = \
102  [velocity[velocityIndex].getSampleMean()
103  for velocity in data.values()]
104  _line, = axes.plot(data.keys(), values)
105  lines.append(_line)
106  legendLabels.append("Flow Profile")
107 
108  if theory:
109  theoryData = \
110  self.getAnalyticShearFlowProfileAsFunctionOfY(shift = shift)
111  _line, = axes.plot(theoryData.keys(), theoryData.values())
112  lines.append(_line)
113  legendLabels.append("Theory")
114 
115  if standardError != 0:
116  if standardError < 0:
117  raise Exception()
118 
119  import math
120  import numpy
121 
122  values = numpy.array(values)
123  sqrt_n = math.sqrt(velocity[velocityIndex].getSampleSize())
124  errors = \
125  numpy.array(
126  [velocity[velocityIndex].getSampleStandardDeviation() /
127  sqrt_n
128  for velocity in data.values()])
129 
130  axes.fill_between(
131  data.keys(),
132  values - standardError * errors,
133  values + standardError * errors,
134  alpha = 0.2)
135 
136  axes.legend(lines, legendLabels)
137 
138  return axes
139 
140 
141  def getFlowProfileAsFunctionOfY(self, shift = True):
142  """
143  Returns the flow profile as a function of `y`.
144 
145  The value returned is a dictionary, with the keys being `y` coordinates
146  in simulation space, and the values being a lists of three
147  `OnTheFlyStatistics` instances, for the flow velocities along the `x`,
148  `y`, and `z` direction, respectively.
149 
150  @param[in] shift
151  Set to `True` to shift the data points along the `y`
152  direction in such a way that a data point lies not at the
153  beginning of the `y` segment it describes, but rather at its
154  center.
155  """
156 
157  from .OnTheFlyStatistics import OnTheFlyStatistics
158  from collections import OrderedDict
159 
160  ret = OrderedDict()
161 
162  for _, xval in self._points.items():
163  for yIndex, yval in xval.items():
164  yCoord = float(yIndex) / self._linearSubdivisions
165  if shift:
166  yCoord += 0.5 / self._linearSubdivisions
167  if yCoord not in ret:
168  ret[yCoord] = \
169  [
170  OnTheFlyStatistics(),
171  OnTheFlyStatistics(),
172  OnTheFlyStatistics()
173  ]
174 
175  for _, zval in yval.items():
176  ret[yCoord][0].mergeSample(zval[0])
177  ret[yCoord][1].mergeSample(zval[1])
178  ret[yCoord][2].mergeSample(zval[2])
179 
180  return ret
181 
182 
183  def getGlobalFlowStatistics(self):
184  """
185  Returns a list of three `OnTheFlyStatistics` objects that combine, one
186  for each of the three Cartesian coordinates, that combines all samples
187  of particle velocities across the simulation volume.
188  """
189 
190  from .OnTheFlyStatistics import OnTheFlyStatistics
191 
192  ret = [OnTheFlyStatistics(), OnTheFlyStatistics(), OnTheFlyStatistics()]
193 
194  for xval in self._points.values():
195  for yval in xval.values():
196  for zval in yval.values():
197  for i in [0, 1, 2]:
198  ret[i].mergeSample(zval[i])
199 
200  return ret
201 
202 
203 
205  self, shearRate = None, shift = True):
206  """
207  Returns the analytic flow profile of a shear flow as a function of `y`.
208 
209  The shear flow is assumed to have the flow direction along the positive
210  `x` direction, and the gradient direction is assumed to be the `y`
211  direction.
212 
213  The value returned is a dictionary, with the keys being `y` coordinates
214  in simulation space, and the values being the mean flow speed along the
215  `x` direction.
216 
217  @param[in] shearRate
218  Sets the shear rate. If `None`, and a run has been added
219  which does specify a shear rate, that value is assumed.
220  Otherwise, an exception is thrown.
221 
222  @param[in] shift
223  Set to `True` to shift the data points along the `y`
224  direction in such a way that a data point lies not at the
225  beginning of the `y` segment it describes, but rather at its
226  center.
227  """
228 
229  if shearRate is None:
230  if not hasattr(self, "_shearRate"):
231  raise Exception()
232  shearRate = self._shearRate
233 
234  if not isinstance(shearRate, float):
235  raise Exception()
236 
237  if not hasattr(self, "_simBoxSizeY"):
238  raise Exception()
239  simBoxSizeY = self._simBoxSizeY
240 
241  if not hasattr(self, "_linearSubdivisions"):
242  raise Exception()
243  linearSubdivisions = self._linearSubdivisions
244 
245  from collections import OrderedDict
246 
247  ret = OrderedDict()
248 
249  maxYIndex = simBoxSizeY * linearSubdivisions - 1
250  for yIndex in range(0, maxYIndex + 1):
251  yCoord = float(yIndex) / linearSubdivisions
252  if shift:
253  yCoord += 0.5 / linearSubdivisions
254 
255  v_x = (yCoord - 0.5 * simBoxSizeY) * shearRate
256  ret[yCoord] = v_x
257 
258  return ret
259 
260 
261  def showVectorFieldGUI(self):
262  import matplotlib.pyplot
263  from .PlotTools import DiscreteSliderWidget
264 
265 
266  fig, ax = matplotlib.pyplot.subplots()
267  ax.set_title("Flow Profile")
268  ax.set_xticks(range(0, self._simBoxSizeX + 1))
269  ax.set_yticks(range(0, self._simBoxSizeY + 1))
270  ax.grid(True, which = 'both')
271  matplotlib.pyplot.xlabel("x")
272  matplotlib.pyplot.ylabel("y")
273  ax.set_xlim([0, self._simBoxSizeX])
274  ax.set_ylim([0, self._simBoxSizeY])
275  matplotlib.pyplot.subplots_adjust(left = 0.1, bottom = 0.25)
276 
277 
278  currentParameters = \
279  {
280  'outputBlockID': 0,
281  'z': 0,
282  }
283 
284  timeAxes = matplotlib.pyplot.axes([0.1, 0.1, 0.8, 0.03])
285  timeSlider = \
286  DiscreteSliderWidget(
287  timeAxes, "Output Block",
288  currentParameters['outputBlockID'], len(self._outputBlocks) - 1,
289  valinit = 0, valfmt = '%0.0f')
290 
291  zAxes = matplotlib.pyplot.axes([0.1, 0.05, 0.8, 0.03])
292  zSlider = \
293  DiscreteSliderWidget(
294  zAxes, "z",
295  currentParameters['z'], self._simBoxSizeZ - 1,
296  valinit = 0, valfmt = '%0.0f')
297 
298  xPoints = [x + 0.5 for x in self._outputBlocks[0].keys()]
299  yPoints = [y + 0.5 for y in self._outputBlocks[1].keys()]
300 
301  import numpy
302  X, Y = numpy.meshgrid(xPoints, yPoints)
303 
304  quiverProxy = []
305 
306  def updateData():
307  outputBlock = self._outputBlocks[currentParameters['outputBlockID']]
308 
309  U = []
310  V = []
311  C = []
312  z = currentParameters['z']
313  for yIdx, y in enumerate(self._outputBlocks[1].keys()):
314  for x in self._outputBlocks[0].keys():
315  v = outputBlock[x][y][z]
316  v = [v[i].getSampleMean() for i in range(0, 3)]
317 
318  if yIdx == len(U):
319  U.append([])
320  V.append([])
321  C.append([])
322 
323  U[yIdx].append(v[0])
324  V[yIdx].append(v[1])
325  C[yIdx].append(v[2])
326 
327  if quiverProxy:
328  quiverProxy[0].set_UVC(U, V, C)
329  else:
330  quiverProxy.append(ax.quiver(X, Y, U, V, C, units = 'width'))
331 
332  fig.canvas.draw_idle()
333 
334  def updateData_time(newOutputBlockID):
335  currentParameters['outputBlockID'] = newOutputBlockID
336  updateData()
337 
338  def updateData_z(newZ):
339  currentParameters['z'] = newZ
340  updateData()
341 
342  timeSlider.on_changed(updateData_time)
343  zSlider.on_changed(updateData_z)
344 
345 
346  updateData()
347 
348  mng = matplotlib.pyplot.get_current_fig_manager()
349  mng.resize(*mng.window.maxsize())
350  matplotlib.pyplot.show()
351 
352 
353  def _setConfigOrCheckCompatibility(self, rundir):
354  """
355  Reads the configuration in the given `rundir`, and either sets it to be
356  the reference configuration if this is the first `rundir` this instance
357  is supplied, or otherwise verifies that the configurations are
358  compatible.
359  """
360 
361  from .Configuration import Configuration
362  config = Configuration(rundir)
363 
364  if not hasattr(self, "_linearSubdivisions"):
365  attributeList = \
366  [
367  "_linearSubdivisions", "_shearRate",
368  "_simBoxSizeX", "_simBoxSizeY", "_simBoxSizeZ",
369  ]
370  for attribute in attributeList:
371  if hasattr(self, attribute):
372  raise Exception()
373 
374  self._linearSubdivisions = 1
375 
376  if "instrumentation.flowProfile.cellSubdivision.y" in config:
377  value = config["instrumentation.flowProfile.cellSubdivision.y"]
378  self._linearSubdivisions = value
379 
380  if "boundaryConditions.LeesEdwards.shearRate" in config:
381  self._shearRate = \
382  config["boundaryConditions.LeesEdwards.shearRate"]
383  else:
384  self._shearRate = 0.0
385 
386  self._simBoxSizeX = config["mpc.simulationBoxSize.x"]
387  self._simBoxSizeY = config["mpc.simulationBoxSize.y"]
388  self._simBoxSizeZ = config["mpc.simulationBoxSize.z"]
389 
390  return
391 
392  linearSubdivisions = 1
393  if "instrumentation.flowProfile.cellSubdivision.y" in config:
394  linearSubdivisions = \
395  config["instrumentation.flowProfile.cellSubdivision.y"]
396 
397  if self._linearSubdivisions != linearSubdivisions:
398  raise Exception()
399 
400  newShearRate = 0.0
401  if "boundaryConditions.LeesEdwards.shearRate" in config:
402  newShearRate = \
403  config["boundaryConditions.LeesEdwards.shearRate"]
404  if self._shearRate != newShearRate:
405  raise Exception()
406 
407  if self._simBoxSizeY != config["mpc.simulationBoxSize.y"]:
408  raise Exception()
409 
410 
411 
412  def _addSampleToPoint(self, slice_, means, standardDeviations, sampleSize):
413  """
414  Adds the data given (`means`, `standardDeviations`, and `sampleSize`) to
415  the point specified by the slice coordinates in `slice_`, which is
416  assumed to be a list of three coordinates (each corresponding to the
417  simulation coordinates after being multiplied with the appropriate
418  `instrumentation.cellSubdivision` configuration value).
419 
420  If no data exists yet for that point, a new instance of
421  `OnTheFlyStatistics` is created.
422  """
423 
424  from .OnTheFlyStatistics import OnTheFlyStatistics
425  from collections import OrderedDict
426 
427  x, y, z = slice_
428  if x not in self._points:
429  self._points[x] = OrderedDict()
430  if y not in self._points[x]:
431  self._points[x][y] = OrderedDict()
432  if z not in self._points[x][y]:
433  self._points[x][y][z] = []
434  self._points[x][y][z].append(OnTheFlyStatistics())
435  self._points[x][y][z].append(OnTheFlyStatistics())
436  self._points[x][y][z].append(OnTheFlyStatistics())
437 
438  newSampleX = \
439  OnTheFlyStatistics(means[0], standardDeviations[0] ** 2, sampleSize)
440  newSampleY = \
441  OnTheFlyStatistics(means[1], standardDeviations[1] ** 2, sampleSize)
442  newSampleZ = \
443  OnTheFlyStatistics(means[2], standardDeviations[2] ** 2, sampleSize)
444 
445  self._points[x][y][z][0].mergeSample(newSampleX)
446  self._points[x][y][z][1].mergeSample(newSampleY)
447  self._points[x][y][z][2].mergeSample(newSampleZ)
MPCDAnalysis.FlowProfile.FlowProfile._setConfigOrCheckCompatibility
def _setConfigOrCheckCompatibility(self, rundir)
Definition: FlowProfile.py:367
MPCDAnalysis.FlowProfile.FlowProfile._linearSubdivisions
_linearSubdivisions
Definition: FlowProfile.py:382
MPCDAnalysis.FlowProfile.FlowProfile.getAnalyticShearFlowProfileAsFunctionOfY
def getAnalyticShearFlowProfileAsFunctionOfY(self, shearRate=None, shift=True)
Definition: FlowProfile.py:233
MPCDAnalysis.FlowProfile.FlowProfile._shearRate
_shearRate
Definition: FlowProfile.py:389
MPCDAnalysis.FlowProfile.FlowProfile._addSampleToPoint
def _addSampleToPoint(self, slice_, means, standardDeviations, sampleSize)
Definition: FlowProfile.py:431
MPCDAnalysis.FlowProfile.FlowProfile.getFlowProfileAsFunctionOfY
def getFlowProfileAsFunctionOfY(self, shift=True)
Definition: FlowProfile.py:160
MPCDAnalysis.FlowProfile.FlowProfile.getGlobalFlowStatistics
def getGlobalFlowStatistics(self)
Definition: FlowProfile.py:194
MPCDAnalysis.FlowProfile.FlowProfile.getMPLAxesForFlowAlongXAsFunctionOfY
def getMPLAxesForFlowAlongXAsFunctionOfY(self, standardError=1, theory=True, shift=True)
Definition: FlowProfile.py:92
MPCDAnalysis.FlowProfile.FlowProfile._simBoxSizeZ
_simBoxSizeZ
Definition: FlowProfile.py:396
MPCDAnalysis.FlowProfile.FlowProfile._outputBlocks
_outputBlocks
Definition: FlowProfile.py:18
MPCDAnalysis.FlowProfile.FlowProfile._simBoxSizeY
_simBoxSizeY
Definition: FlowProfile.py:395
MPCDAnalysis.FlowProfile.FlowProfile.__init__
def __init__(self, rundir)
Definition: FlowProfile.py:11
MPCDAnalysis.FlowProfile.FlowProfile.addRun
def addRun(self, rundir)
Definition: FlowProfile.py:28
MPCDAnalysis.FlowProfile.FlowProfile._lastRun
_lastRun
Definition: FlowProfile.py:15
MPCDAnalysis.FlowProfile.FlowProfile._points
_points
Definition: FlowProfile.py:17
MPCDAnalysis.FlowProfile.FlowProfile._simBoxSizeX
_simBoxSizeX
Definition: FlowProfile.py:394