def Map(self, coord): if optimise.DebuggingEnabled(): assert (len(coord) == 3) assert (coord[0] >= self._a and coord[0] <= self._b) assert (coord[2] <= self._maxZ) assert (coord[2] >= self._minZ) distanceFromBottom = coord[2] - self._minZ distanceFromInnerWall = coord[0] - self._a distanceFromOuterWall = self._b - coord[0] if calc.AlmostEquals(self._phiTop, 0.0): localMaxZ = self._maxZ elif self._phiTop > 0.0: localMaxZ = self._maxZ - distanceFromOuterWall * math.tan( self._phiTop) else: localMaxZ = self._maxZ + distanceFromInnerWall * math.tan( self._phiTop) if calc.AlmostEquals(self._phiBottom, 0.0): localMinZ = self._minZ elif self._phiBottom > 0.0: localMinZ = self._minZ + distanceFromInnerWall * math.tan( self._phiBottom) else: localMinZ = self._minZ - distanceFromOuterWall * math.tan( self._phiBottom) newZ = localMinZ + (distanceFromBottom / (self._maxZ - self._minZ)) * (localMaxZ - localMinZ) return [coord[0], coord[1], newZ]
def SliceCoordsLinear(minVal, maxVal, minL, divisions, tolerance=calc.Epsilon()): """ Generate one dimension of annulus slice coordinates based upon the supplied geometry information, with linearly stretched node spacing """ assert (minL > 0.0) assert (divisions >= 4) assert (calc.IsEven(divisions)) d = (maxVal - minVal) / 2.0 n = divisions / 2 # Perform a binary search for r - using a numerical solve via # scipy.optimize.fsolve seems to introduce too large an error minR = 1.0 maxR = d / minL r = 0.0 while True: # Calculate a new guess for r oldR = r r = (maxR + minR) / 2.0 if calc.AlmostEquals(r, oldR, tolerance=tolerance): break # Calculate what value of d this implies dGuess = 0.0 for i in range(n): dGuess += minL * (r**i) # Based upon the implied d, choose new bounds for r if calc.AlmostEquals(d, dGuess, tolerance=tolerance): break elif dGuess > d: maxR = r else: minR = r if calc.AlmostEquals(r, 1.0, tolerance=tolerance): raise Exception("No solution for r > 1.0 found") debug.dprint("r = " + str(r), 2) coords = [minVal] for i in range(divisions / 2 - 1): coords.insert(i + 1, coords[i] + (minL * (r**i))) coords.append((maxVal + minVal) / 2.0) coords.append(maxVal) for i in range(divisions / 2 - 1): coords.insert(len(coords) - i - 1, coords[-i - 1] - (minL * (r**i))) return coords
def UsedDimCoordMask(self): """ Return a mask, masking dimensions unused in the bounding box """ return [ not calc.AlmostEquals( self._lbound[i], self._ubound[i], tolerance=self._dimTolerance) for i in range(self.Dim()) ]
def ToVtu(self, axis=(0.0, 1.0, 0.0), quadMesh=False): assert (not self._shape is None) vtu = self.Mesh(quadMesh=quadMesh).ToVtu() name = self.GetName() if name is None: name = "UnknownField" data = [] for i in range(self.YCoordsCount()): for j in range(self.XCoordsCount()): data.append(self.GetVal(j, i)) data = numpy.array(data) data.shape = (self.XCoordsCount() * self.YCoordsCount(), self._DataLen()) vtu.AddField(name, data) if not calc.AlmostEquals(axis[0], 0.0) or not calc.AlmostEquals( axis[1], 1.0) or not calc.AlmostEquals(axis[2], 0.0): transform = vtk.vtkTransform() transform.Identity() # Find the rotation axis # (0, 1, 0) x axis rotationAxis = [-axis[2], 0.0, -axis[0]] # Normalise rotationAxisMagnitude = calc.L2Norm(rotationAxis) rotationAxis = [ val / rotationAxisMagnitude for val in rotationAxis ] # Find the rotation angle angle = calc.Rad2Deg(math.acos(axis[1] / calc.L2Norm(axis))) # Rotation transform.RotateWXYZ(angle, rotationAxis[0], rotationAxis[1], rotationAxis[2]) transform.Update() newPoints = vtk.vtkPoints() transform.TransformPoints(vtu.ugrid.GetPoints(), newPoints) vtu.ugrid.SetPoints(newPoints) return vtu
def UsedDim(self): """ Return the dimensions actually used in the bounding box """ dim = 0 for i in range(len(self._lbound)): if not calc.AlmostEquals(self._lbound[i], self._ubound[i], tolerance=self._dimTolerance): dim += 1 return dim
def CylinderVtuCut(inputVtu, radius, origin=(0.0, 0.0, 0.0), axis=(0.0, 0.0, 1.0)): """ Perform a 3D cylinder cut of a vtu """ assert (len(origin) == 3) assert (len(axis) == 3) # An implicit function with which to cut cylinder = vtk.vtkCylinder() cylinder.SetRadius(radius) cylinder.SetCenter((0.0, 0.0, 0.0)) # Generate the transform transform = vtk.vtkTransform() transform.Identity() if not calc.AlmostEquals(axis[0], 0.0) or not calc.AlmostEquals( axis[1], 1.0) or not calc.AlmostEquals(axis[2], 0.0): # Find the rotation axis # (0, 1, 0) x axis rotationAxis = [-axis[2], 0.0, -axis[0]] # Normalise rotationAxisMagnitude = calc.L2Norm(rotationAxis) rotationAxis = [val / rotationAxisMagnitude for val in rotationAxis] # Find the rotation angle angle = calc.Rad2Deg(math.acos(axis[1] / calc.L2Norm(axis))) # Rotation transform.RotateWXYZ(angle, rotationAxis[0], rotationAxis[1], rotationAxis[2]) # Translation transform.Translate(origin[0], origin[1], origin[2]) # Set the transform cylinder.SetTransform(transform) return ImplicitFunctionVtuCut(inputVtu, cylinder)
def LineVtuCut(inputVtu, origin=(0.0, 0.0, 0.0), direction=(1.0, 0.0, 0.0)): """ Perform a plane-plane double cut to form a 1D line (in 3D space) """ assert (len(origin) == 3) assert (len(direction) == 3) # Copy the input line direction x0 = direction[0] y0 = direction[1] z0 = direction[2] # To form the line from two planar cuts, we need two normal vectors at right # angles to the line direction. # Form the first normal vector by imposing x0 dot x1 = 0, with one component # of x1 equal to one and one equal to zero, where the component in x0 # corresponding to the remaining third component is non-zero if calc.AlmostEquals(z0, 0.0): if calc.AlmostEquals(y0, 0.0): if calc.AlmostEquals(x0, 0.0): raise Exception("Direction has zero length") y1 = 1.0 z1 = 0.0 # x1 = -(y0 y1 + z0 z1) / x0 x1 = -y0 / x0 else: x1 = 1.0 z1 = 0.0 # y1 = -(x0 x1 + z0 z1) / y0 y1 = -x0 / y0 else: x1 = 1.0 y1 = 0.0 # z1 = -(x0 x1 + y0 y1) / z0 z1 = -x0 / z0 # Normalise the first normal vector mag = calc.L2Norm([x1, y1, z1]) x1 /= mag y1 /= mag z1 /= mag # Form the second normal vector via a cross product x2 = y0 * z1 - z0 * y1 y2 = z0 * x1 - x0 * z1 z2 = x0 * y1 - y0 * x1 # Normalise the second normal vector mag = calc.L2Norm([x2, y2, z2]) x2 /= mag y2 /= mag z2 /= mag normal1 = (x1, y1, z1) normal2 = (x2, y2, z2) debug.dprint("Normal 1 = " + str(normal1)) debug.dprint("Normal 2 = " + str(normal2)) # Perform the cuts cutVtu = PlanarVtuCut(inputVtu, origin, normal1) cutVtu = PlanarVtuCut(cutVtu, origin, normal2) return cutVtu
def ModelPvtuToVtu(pvtu): """ Convert a parallel vtu to a serial vtu but without any fields. Does nothing (except generate a copy) if the supplied vtu is already a serial vtu. """ # Step 1: Extract the ghost levels, and check that we have a parallel vtu result = vtu() ghostLevel = pvtu.ugrid.GetCellData().GetArray("vtkGhostLevels") if ghostLevel is None: # We have a serial vtu debug.deprint("Warning: VtuFromPvtu passed a serial vtu") ghostLevel = [0 for i in range(vtu.ugrid.GetNumberOfCells())] else: # We have a parallel vtu ghostLevel = [ ghostLevel.GetValue(i) for i in range(ghostLevel.GetNumberOfComponents() * ghostLevel.GetNumberOfTuples()) ] # Step 2: Collect the non-ghost cell IDs debug.dprint("Input cells: " + str(pvtu.ugrid.GetNumberOfCells())) cellIds = [] keepCell = [False for i in range(pvtu.ugrid.GetNumberOfCells())] oldCellIdToNew = [None for i in range(pvtu.ugrid.GetNumberOfCells())] # Collect the new non-ghost cell IDs and generate the cell renumbering map index = 0 for i, level in enumerate(ghostLevel): if calc.AlmostEquals(level, 0.0): cellIds.append(i) keepCell[i] = True oldCellIdToNew[i] = index index += 1 debug.dprint("Non-ghost cells: " + str(len(cellIds))) # Step 3: Collect the non-ghost node IDs debug.dprint("Input points: " + str(pvtu.ugrid.GetNumberOfPoints())) nodeIds = [] keepNode = [False for i in range(pvtu.ugrid.GetNumberOfPoints())] oldNodeIdToNew = [None for i in range(pvtu.ugrid.GetNumberOfPoints())] # Find a list of candidate non-ghost node IDs, based on nodes attached to # non-ghost cells for cellId in cellIds: cellNodeIds = pvtu.ugrid.GetCell(cellId).GetPointIds() cellNodeIds = [ cellNodeIds.GetId(i) for i in range(cellNodeIds.GetNumberOfIds()) ] for nodeId in cellNodeIds: keepNode[nodeId] = True debug.dprint("Non-ghost nodes (pass 1): " + str(keepNode.count(True))) # Detect duplicate nodes # Jumping through Python 2.3 hoops for cx1 - in >= 2.4, can just pass a cmp # argument to list.sort class LocationSorter(utils.Sorter): def __init__(self, x, y, order=[0, 1, 2]): utils.Sorter.__init__(self, x, y) self._order = order return def __cmp__(self, val): def cmp(x, y, order): for comp in order: if x[comp] > y[comp]: return 1 elif x[comp] < y[comp]: return -1 return 0 return cmp(self._key, val.GetKey(), self._order) def Dup(x, y, tol): for i, xVal in enumerate(x): if abs(xVal - y[i]) > tol: return False return True locations = pvtu.GetLocations() lbound, ubound = VtuBoundingBox(pvtu).GetBounds() tol = calc.L2Norm([ubound[i] - lbound[i] for i in range(len(lbound))]) / 1.0e12 debug.dprint("Duplicate node tolerance: " + str(tol)) duplicateNodeMap = [None for i in range(pvtu.ugrid.GetNumberOfPoints())] duplicateNodeMapInverse = [[] for i in range(len(duplicateNodeMap))] # We need to sort the locations using all possible combinations of component # order, to take account of all possible floating point errors. orders = [[0], [[0, 1], [1, 0]], [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]][VtuDim(pvtu) - 1] for order in orders: debug.dprint("Processing component order: " + str(order)) # Generate a sorted list of locations, with their node IDs sortPack = [ LocationSorter(location.tolist(), i, order) for i, location in enumerate(locations) ] sortPack.sort() permutedLocations = [pack.GetKey() for pack in sortPack] permutedNodeIds = [pack.GetValue() for pack in sortPack] # This rather horrible construction maps all except the first node in each set # of duplicate nodes to the first node in the set of duplicate nodes, for the # sorted current non-ghost locations i = 0 while i < len(permutedLocations) - 1: j = i while j < len(permutedLocations) - 1: if Dup(permutedLocations[i], permutedLocations[j + 1], tol): if keepNode[permutedNodeIds[j + 1]]: oldNodeId = permutedNodeIds[j + 1] newNodeId = permutedNodeIds[i] while not duplicateNodeMap[newNodeId] is None: newNodeId = duplicateNodeMap[newNodeId] if newNodeId == oldNodeId: # This is already mapped the other way break if newNodeId == oldNodeId: # Can only occur from early exit of the above loop j += 1 continue def MapInverses(oldNodeId, newNodeId): for nodeId in duplicateNodeMapInverse[oldNodeId]: assert (not nodeId == newNodeId) assert (keepNode[newNodeId]) keepNode[nodeId] = False duplicateNodeMap[nodeId] = newNodeId duplicateNodeMapInverse[newNodeId].append( nodeId) MapInverses(nodeId, newNodeId) duplicateNodeMapInverse[oldNodeId] = [] return keepNode[newNodeId] = True keepNode[oldNodeId] = False # Map everything mapped to the old node ID to the new node ID MapInverses(oldNodeId, newNodeId) duplicateNodeMap[oldNodeId] = newNodeId duplicateNodeMapInverse[newNodeId].append(oldNodeId) j += 1 else: break i = j i += 1 debug.dprint("Non-ghost nodes: " + str(keepNode.count(True))) # Collect the final non-ghost node IDs and generate the node renumbering map nodeIds = [] index = 0 for i, keep in enumerate(keepNode): if keep: nodeIds.append(i) oldNodeIdToNew[i] = index index += 1 for i, nodeId in enumerate(duplicateNodeMap): if not nodeId is None: assert (oldNodeIdToNew[i] is None) assert (not oldNodeIdToNew[nodeId] is None) oldNodeIdToNew[i] = oldNodeIdToNew[nodeId] debug.dprint("Non-ghost nodes (pass 2): " + str(len(nodeIds))) # Step 4: Generate the new locations locations = pvtu.GetLocations() locations = numpy.array([locations[i] for i in nodeIds]) points = vtk.vtkPoints() points.SetDataTypeToDouble() for location in locations: points.InsertNextPoint(location) result.ugrid.SetPoints(points) # Step 5: Generate the new cells for cellId in cellIds: cell = pvtu.ugrid.GetCell(cellId) cellNodeIds = cell.GetPointIds() cellNodeIds = [ cellNodeIds.GetId(i) for i in range(cellNodeIds.GetNumberOfIds()) ] idList = vtk.vtkIdList() for nodeId in cellNodeIds: oldNodeId = nodeId nodeId = oldNodeIdToNew[nodeId] assert (not nodeId is None) assert (nodeId >= 0) assert (nodeId <= len(nodeIds)) idList.InsertNextId(nodeId) result.ugrid.InsertNextCell(cell.GetCellType(), idList) return result, oldNodeIdToNew, oldCellIdToNew
def JoinStat(*args): """ Joins a series of stat files together. Useful for combining checkpoint .stat files. Selects data in later stat files over earlier stat files. Assumes data in stat files are sorted by ElapsedTime. """ nStat = len(args) assert (nStat > 0) times = [stat["ElapsedTime"] for stat in args] startT = [t[0] for t in times] permutation = utils.KeyedSort(startT, range(nStat)) stats = [args[index] for index in permutation] startT = [startT[index] for index in permutation] times = [times[index] for index in permutation] endIndices = numpy.array([len(time) for time in times], dtype=int) for i, t in enumerate(times[:-1]): for j, time in enumerate(t): if calc.AlmostEquals(startT[i + 1], time, tolerance=1.0e-6): endIndices[i] = max(j - 1, 0) break elif startT[i + 1] < time: endIndices[i] = j break debug.dprint("Time ranges:") if len(times) > 0: for i in range(nStat): debug.dprint((startT[i], times[i][endIndices[i] - 1])) else: debug.dprint("No data") dataIndices = numpy.empty(len(args) + 1, dtype=int) dataIndices[0] = 0 for i, index in enumerate(endIndices): dataIndices[i + 1] = dataIndices[i] + index stat = stats[0] data = {} for key in stat.keys(): arr = stat[key] shape = list(arr.shape) shape[0] = dataIndices[-1] data[key] = numpy.empty(shape, dtype=arr.dtype) data[key][:dataIndices[1]] = arr[:endIndices[0]] data[key][dataIndices[1]:] = calc.Nan() delimiter = stat.GetDelimiter() for i in range(1, nStat): stat = stats[i] for key in stat.keys(): arr = stat[key] if not key in data: shape = list(arr.shape) shape[0] = dataIndices[-1] data[key] = numpy.empty(shape, dtype=arr.dtype) data[key][:dataIndices[i]] = calc.Nan() data[key][dataIndices[i + 1]:] = calc.Nan() data[key][dataIndices[i]:dataIndices[i + 1]] = arr[:endIndices[i]] output = Stat(delimiter=delimiter) for key in data.keys(): output[key] = numpy.array(data[key]) return output
def ModelPvtuToVtu(pvtu): """ Convert a parallel vtu to a serial vtu but without any fields. Does nothing (except generate a copy) if the supplied vtu is already a serial vtu. """ # Step 1: Extract the ghost levels, and check that we have a parallel vtu result = vtu() ghostLevel = pvtu.ugrid.GetCellData().GetArray("vtkGhostLevels") if ghostLevel is None: # We have a serial vtu debug.deprint("Warning: input file contains no vtkGhostLevels") ghostLevel = [0 for i in range(pvtu.ugrid.GetNumberOfCells())] else: # We have a parallel vtu ghostLevel = [ ghostLevel.GetValue(i) for i in range(ghostLevel.GetNumberOfComponents() * ghostLevel.GetNumberOfTuples()) ] # Step 2: Collect the non-ghost cell IDs debug.dprint("Input cells: " + str(pvtu.ugrid.GetNumberOfCells())) cellIds = [] keepCell = [False for i in range(pvtu.ugrid.GetNumberOfCells())] oldCellIdToNew = [None for i in range(pvtu.ugrid.GetNumberOfCells())] # Collect the new non-ghost cell IDs and generate the cell renumbering map index = 0 for i, level in enumerate(ghostLevel): if calc.AlmostEquals(level, 0.0): cellIds.append(i) keepCell[i] = True oldCellIdToNew[i] = index index += 1 debug.dprint("Non-ghost cells: " + str(len(cellIds))) # Step 3: Collect the non-ghost node IDs debug.dprint("Input points: " + str(pvtu.ugrid.GetNumberOfPoints())) keepNode = [False for i in range(pvtu.ugrid.GetNumberOfPoints())] # Find a list of candidate non-ghost node IDs, based on nodes attached to # non-ghost cells keepNodeCount = 0 for cellId in cellIds: cellNodeIds = pvtu.ugrid.GetCell(cellId).GetPointIds() cellNodeIds = [ cellNodeIds.GetId(i) for i in range(cellNodeIds.GetNumberOfIds()) ] keepNodeCount += len(cellNodeIds) for nodeId in cellNodeIds: keepNode[nodeId] = True uniqueKeepNodeCount = keepNode.count(True) debug.dprint("Non-ghost nodes (pass 1): " + str(uniqueKeepNodeCount)) if uniqueKeepNodeCount == keepNodeCount: debug.dprint("Assuming pvtu is discontinuous") # we're keeping all non-ghost nodes: nodeIds = [ i for i in range(pvtu.ugrid.GetNumberOfPoints()) if keepNode[i] ] oldNodeIdToNew = numpy.array([None] * pvtu.ugrid.GetNumberOfPoints()) oldNodeIdToNew[nodeIds] = range(keepNodeCount) else: # for the CG case we still have duplicate nodes that need to be removed oldNodeIdToNew, nodeIds = PvtuToVtuRemoveDuplicateNodes(pvtu, keepNode) # Step 4: Generate the new locations locations = pvtu.GetLocations() locations = numpy.array([locations[i] for i in nodeIds]) points = vtk.vtkPoints() points.SetDataTypeToDouble() for location in locations: points.InsertNextPoint(location) result.ugrid.SetPoints(points) # Step 5: Generate the new cells for cellId in cellIds: cell = pvtu.ugrid.GetCell(cellId) cellNodeIds = cell.GetPointIds() cellNodeIds = [ cellNodeIds.GetId(i) for i in range(cellNodeIds.GetNumberOfIds()) ] idList = vtk.vtkIdList() for nodeId in cellNodeIds: oldNodeId = nodeId nodeId = oldNodeIdToNew[nodeId] assert (not nodeId is None) assert (nodeId >= 0) assert (nodeId <= len(nodeIds)) idList.InsertNextId(nodeId) result.ugrid.InsertNextCell(cell.GetCellType(), idList) return result, oldNodeIdToNew, oldCellIdToNew