def makeHSADistribution(num0, num1, num2, num3, num4, num5, x, y, z, minDist): # create the cubic distribution cubeGen = VPCBBG('VolumePackCuboid.txt') # create the NPack object. numPoints = num0 + num1 + num2 + num3 + num4 + num5 # generate the building block SpacePackBB = cubeGen.generateBuildingBlock(numPoints, -x / 2, x / 2, -y / 2, y / 2, -z / 2, z / 2, minDist) # dump the base points to file fIO.saveXYZ(SpacePackBB.blockXYZVals, 'CA', 'points.xyz') # generate sufficient random orientiation vectors thetaPhiArray = [ coords.pickRandomPointOnUnitSphere() for _ in range(numPoints) ] directors = [ coords.sphericalPolar2XYZ(np.array([1.0, angs[0], angs[1]])) for angs in thetaPhiArray ] xyzVals0, names0 = genHsaPoints('hsa_apo.xyz', SpacePackBB.blockXYZVals[0:num0], directors[0:num1]) xyzVals1, names1 = genHsaPoints('hsa_with1.xyz', SpacePackBB.blockXYZVals[num0:num0 + num1], directors[num0:num0 + num1]) xyzVals2, names2 = genHsaPoints( 'hsa_with2.xyz', SpacePackBB.blockXYZVals[num0 + num1:num0 + num1 + num2], directors[num0 + num1:num0 + num1 + num2]) xyzVals3, names3 = genHsaPoints( 'hsa_with3.xyz', SpacePackBB.blockXYZVals[num0 + num1 + num2:num0 + num1 + num2 + num3], directors[num0 + num1 + num2:num0 + num1 + num2 + num3]) xyzVals4, names4 = genHsaPoints( 'hsa_with4.xyz', SpacePackBB.blockXYZVals[num0 + num1 + num2 + num3:num0 + num1 + num2 + num3 + num4], directors[num0 + num1 + num2 + num3:num0 + num1 + num2 + num3 + num4]) xyzVals5, names5 = genHsaPoints( 'hsa_with5.xyz', SpacePackBB.blockXYZVals[num0 + num1 + num2 + num3 + num4:], directors[num0 + num1 + num2 + num3 + num4:]) fIO.saveXYZList(xyzVals0, names0, 'hsa0All.xyz') fIO.saveXYZList(xyzVals1, names1, 'hsa1All.xyz') fIO.saveXYZList(xyzVals2, names2, 'hsa2All.xyz') fIO.saveXYZList(xyzVals3, names3, 'hsa3All.xyz') fIO.saveXYZList(xyzVals4, names4, 'hsa4All.xyz') fIO.saveXYZList(xyzVals5, names5, 'hsa5All.xyz')
def pickFirstPoints(self): from Library.VolumePackCuboid import VolumePackCuboidBBG as VPCBBG BBGen = VPCBBG(self.paramFilename) # generate seedLength number of randomly disributed particles in the void. BB = BBGen.generateBuildingBlock(self.seedLength, self.xRange1, self.xRange2, self.yRange1, self.yRange2, self.zRange1, self.zRange2, self.minDist) self.nList = BB.blockXYZVals #self.nList = [np.array([rnd.uniform(self.xRange1, self.xRange2), rnd.uniform(self.yRange1, self.yRange2), rnd.uniform(self.zRange1, self.zRange2)])] self.nAttempts = self.seedLength * [0] self.lenListForProximityScore, self.proximityScore = self.computeEntireScore(self.nList, [self.r0]) return True
def initialiseParameters(self): # ensure parent initialisation takes place and core values are initialised BBG.initialiseParameters(self) self.particleName = self.getParam('particleName') self.CPPBBG = CPPBBG(self.paramFilename) self.VPCBBG = VPCBBG(self.paramFilename) if self.noLoadErrors == False: print("Critical Parameters are undefined for constrained polymer object") sys.exit()
def makeWormNetwork(numNodesInit, nodeDist, xSize, ySize, zSize, WormRadius, MaxTubeLength, MaxNumAttempts, ValidNodesPerCluster, minDist, packingFraction): VolumeBBG = VPCBBG('VolumePackCuboid.txt') # generate the XYZVals of the network nodes packed in the volume NetworkNodesXYZ = VolumeBBG.generateBuildingBlock(numNodesInit, -xSize / 2, xSize / 2, -ySize / 2, ySize / 2, -zSize / 2, zSize / 2, nodeDist) fIO.saveXYZ(NetworkNodesXYZ.blockXYZVals, 'O', 'cubepackedPoints.xyz') # create a list of groups of indices which together form a good network ListOfNetworkGroups = groupNodesInNetwork(NetworkNodesXYZ.blockXYZVals, WormRadius, MaxTubeLength, ValidNodesPerCluster, MaxNumAttempts) # dump output as file outputWormNetworkAsAtoms(ListOfNetworkGroups, NetworkNodesXYZ.blockXYZVals, TubeRadius) # for each cluster in the network generate a series of points and directors networkPoints = populateClusters(ListOfNetworkGroups, NetworkNodesXYZ.blockXYZVals, WormRadius, minDist, packingFraction) # repack each list xyzVals = [points[0] for points in networkPoints] directors = [points[1] for points in networkPoints] names = [points[2] for points in networkPoints] # return flattened lists return ([point for minorList in xyzVals for point in minorList], [director for minorList in directors for director in minorList ], [name for minorList in names for name in minorList])
import random as rnd import copy as cp from Library.randomPolymer import RandomPolymerPackBBG as RPBBG from Library.constrainedPolymer import ConstrainedPolymerPackBBG as CPBBG #from Library.VolumePackSquareBasedPyramid import VolumePackSquareBasedPyramidBBG as VPSBPBBG from Library.VolumePackCuboid import VolumePackCuboidBBG as VPCBBG from Projects.spiral import Spiral #from Projects.Kagome import computeKagomeLattice as CKL import Utilities.fileIO as fIO import Utilities.coordSystems as coords if __name__=="__main__": # create the NPack and polymer objects. CuboidPackBBG = VPCBBG("VolumePackCuboid.txt") ConstrainedPolyGen = CPBBG("ConstrainedPolymer.txt") PolyGen = RPBBG("RandomPolymer.txt") BoxX = 250 BoxY = 250 BoxZ = 70 # Spiral Params num3Spirals = 2 num4Spirals = 2 num5Spirals = 2 numStraightPolymers = 4 numHelicalPolymers = 4 numParticlesArm = 40 bondLength = 1.5
class BranchedPolymerPackBBG(BBG): ''' Overloads the building block generator class, and makes use of the constrained polymer class, to generate a set of polymer chains between the nodes of arbitary graphs drawn from a set of N points. Each distinct graph is called a cluster. The user specifies a set containing the allowed numbers of nodes per cluster, and these are distributed randomly. Sets of 2,4 5 and 7 nodes gives rise to a 0, 1 2 or 3 branch points per cluster. Each polymer that connects two nodes of a given graph is drawn using the constrained polymer class. The rest of the network can be passed in as pointsToAvoid to avoid a self crossing. But this increases the cost of the calculation. ''' def __init__(self, filename): BBG.__init__(self, filename) def initialiseParameters(self): # ensure parent initialisation takes place and core values are initialised BBG.initialiseParameters(self) self.particleName = self.getParam('particleName') self.CPPBBG = CPPBBG(self.paramFilename) self.VPCBBG = VPCBBG(self.paramFilename) if self.noLoadErrors == False: print("Critical Parameters are undefined for constrained polymer object") sys.exit() def generateBuildingBlock( self, numPoints, x1, x2, y1, y2, z1, z2, networkMinDist, neighborRadius, monomerMinDist, bondLength, curlyFactor, coneAngle, minAngle, threshold, maxNeighbours, angularRange=['None'], pointsToAvoid=[], visualiseEnvelope=(0,20), envelopeList=["None"]): self.numPoints = numPoints self.bondLength = float(bondLength) self.networkMinDist = networkMinDist self.neighborRadius = neighborRadius self.monomerMinDist = monomerMinDist self.x1 = x1 self.x2 = x2 self.y1 = y1 self.y2 = y2 self.z1 = z1 self.z2 = z2 self.coneAngle = coneAngle self.minAngle = minAngle self.threshold = threshold self.maxNeighbours = maxNeighbours self.curlyFactor = curlyFactor self.pointsToAvoid = pointsToAvoid self.visualiseEnvelope = visualiseEnvelope self.envelopeList = envelopeList self.angularRange = angularRange return BBG.generateBuildingBlock(self, numPoints, networkMinDist, envelopeList=envelopeList, visualiseEnvelope=visualiseEnvelope, pointsToAvoid=pointsToAvoid) def generateBuildingBlockDirector(self): return np.array([0.0, 0.0, 1.0]) def generateBuildingBlockRefPoint(self): return np.array( [ ( self.x1 + self.x2 ) / 2.0, ( self.y1 + self.y2 ) / 2.0, ( self.z1 + self.z2 ) / 2.0 ] ) def generateBuildingBlockNames(self): return [self.particleName] * self.numPoints def generateBuildingBlockXYZ(self): # pick n random points in the specified envelope within a cubic region of space. points = self.VPCBBG.generateBuildingBlock(self.numPoints, self.x1, self.x2, self.y1, self.y2, self.z1, self.z2, self.networkMinDist, envelopeList=self.envelopeList, pointsToAvoid=self.envelopeList, visualiseEnvelope=self.visualiseEnvelope) if self.dumpInterimFiles==1: fIO.saveXYZ(points.blockXYZVals, 'C', 'pointsInCube.xyz') # generate a random graph of connections between all the points, # such that no node has more than 3 connections and every node has at least one edge g = self.splitPointsIntoGraphs(points.blockXYZVals) if self.dumpInterimFiles==1: self.visualiseNetwork(g, points.blockXYZVals, self.monomerMinDist) return self.connectGraphWithConstrainedPolymer(g, points.blockXYZVals) def visualiseNetwork(self, g, points, radius): dZ = 0.1 * radius # ten particles per radius xyzVals = [] for edge in g.edges: node1 = points[edge[0]] node2 = points[edge[1]] director = node2 - node1 length = np.linalg.norm(director) director = director/length numPoints = int(length/dZ) xyzVals += [ node1 + float(zIndex) * dZ * director for zIndex in range(0, numPoints) ] fIO.saveXYZ(xyzVals, 'P', 'rawNetwork.xyz') def splitPointsIntoGraphs(self, points): # nodes in graph are numbered 0 to numNodes - 1 # which correspond to the index in points. numNodes = len(points) # set up an empty graph g = nx.Graph() # add the nodes to the graph for nodeNum in range(0, numNodes): g.add_node(nodeNum) params=[self.neighborRadius, self.coneAngle, self.minAngle, self.threshold, self.maxNeighbours] # Add the edges and return return self.add_edges(g, points, params, mode="natural") # switcher between edge adding algorithms def add_edges(self, g, points, params, mode="natural"): if mode=="deterministic": outputGraph = self.add_edges_deterministic(g) if mode=="natural": outputGraph = self.add_edges_natural(g, points, params[0], params[1], params[2], params[3], params[4]) # find singleton nodes nodesToRemove=[] for node in outputGraph.nodes(): if sum(1 for _ in g.neighbors(node))==0: nodesToRemove += [node] # remove singleton nodes for node in nodesToRemove: outputGraph.remove_node(node) return outputGraph # randomly picks two points less than radius apart and uses that as the axis for a cone of interior angle coneangle # Then picks point inside that cone, such that no points subtend an angle less than minAngle. # keep going until threshold percentage of points have at least one edge def add_edges_natural(self, g, points, neighborRadius, coneAngle, minAngle, threshold, maxEdgesPerNode): numPoints = len(points) numFreeNodes = self.countFreeNodes(g) minNumFreeNodes = int((1.0 - threshold) * float(numPoints)) while ( numFreeNodes > minNumFreeNodes): # pick a node at random node1 = rnd.randint(0, numPoints-1) # only pick a node if it has less than maxEdgesPerNode if sum(1 for _ in g.neighbors(node1)) < maxEdgesPerNode: # pick a second node at random node2 = rnd.randint(0, numPoints-1) # compute director between vector if node1 and node 2 are not the same node if not node1==node2: director = points[node2] - points[node1] length = np.linalg.norm(director) directorHat = director/length # only pick second node if it not node 1, has less than maxEdgesPerNode and is within radius of the first node if (not node1==node2) and (sum(1 for _ in g.neighbors(node2)) < maxEdgesPerNode) and length < neighborRadius: # add edge between the two root nodes g.add_edge(node1, node2) # begin a list of points in the current chain that we are making xyzList = [ points[node1], points[node2] ] # generate a list of points that are in the allowed cone coneList = self.pickPointsInCone(points[node1], coneAngle, directorHat, points, xyzList) # loop through the cone list checking each point in order to see # a) it's less than radius from last point on list # b) forms an angle greater than minAngle with previous 2 points on list # c) has less edges than maxEdgesPerNode lastNode = node2 for node in coneList: if (np.linalg.norm(points[node] - xyzList[-1]) < neighborRadius ): if coords.bondAngle(xyzList[-2], xyzList[-1], points[node]) > minAngle: if (sum(1 for _ in g.neighbors(node2)) < maxEdgesPerNode): g.add_edge(lastNode, node) xyzList += points[node] lastNode = node numFreeNodes = self.countFreeNodes(g) return g # returns a list of indices of points in pointsList that are within a cone # defined by apex, director and cone angle. # Sorts the list of indices by the distance from the apex point along the cone axis def pickPointsInCone(self, apex, coneAngle, director, pointsList, excludedPoints): indexList = [] tanAngle = np.tan(coneAngle/2) for index, point in enumerate(pointsList): if not True in [ np.linalg.norm(point - testPoint)<1e-6 for testPoint in excludedPoints ]: # ignore the two points that were used to define the cone z = np.dot(point - apex, director) y = np.linalg.norm(point - apex - z*director) if y/z <= tanAngle and z > float(0.0) : indexList += [(index, z)] indexList = sorted(indexList, key=lambda a:a[1]) return [ item[0] for item in indexList] def countFreeNodes(self, g): return sum( 1 for node in g.nodes if sum(1 for _ in g.neighbors(node))==0 ) def add_edges_deterministic(self, g): # loop through the nodes once and add one two or 3 edges to the node. for node in g.nodes(): # randomly choose how many edges this node should have numEdgesToAdd = rnd.choice([1,2,3]) # count number of current edges on the node numCurrentEdges = sum(1 for _ in g.neighbors(node)) # reduce number of edges numEdgesToAdd = numEdgesToAdd - numCurrentEdges # only add edges to this node if we still need to if numEdgesToAdd>0: # finds, or creates the right number of nodes that can # still be connected to (excluding the current node). freeNodes = self.findFreeNodes(g, [node], numEdgesToAdd) # connect the chosen nodes to the current node for node2 in freeNodes: # add an edge between the node and the selected free nodes g.add_edge(node, node2) return g def findFreeNodes(self, g, protectedNodes, targetNumFreeNodes): freeNodes = [] # make sure the requested number of nodes is smaller than the number of nodes # we are allowed to look at (total # of nodes - # of protected nodes) if targetNumFreeNodes <= ( len(g) - len(protectedNodes) ): # first look for nodes with no neighbours, then 1, then 2. for targetNumEdges in [0, 1, 2]: # loop through the nodes (backwards!) and look for nodes with targetNumEdges # backwards encourages a more broken up network with several components for node in range(len(g)-1, 0, -1): # make sure the current node is not on the protected list if not node in protectedNodes: # if the current node has the targetNumEdges then add it to the free # node list. if (sum(1 for _ in g.neighbors(node))==targetNumEdges): freeNodes+=[node] # break the inner loop if we already have enough nodes if len(freeNodes)==targetNumFreeNodes: break # break the outer loop if we already have enough nodes if len(freeNodes)==targetNumFreeNodes: break # if we got through the 0, 1 and 2 nodes and there aren't enough free nodes # we need to disconnect some edges. # If we are in this state then we know that all the nodes # have 3 edges and there are enough non-protected nodes to generate the free_list if (len(freeNodes)<targetNumFreeNodes)==True: # loop through unprotected nodes and remove an edge # add the current node to the free nodes. # The second node is also free, but don't want to do an insertion. # might make for a dull networks.So this will leave 3 nodes open - will help # with later nodes anyway. for node in g.nodes(): if not node in protectedNodes: for node2 in g.neighbours(node): g.remove_edge(node, node2) break # only take the first neighbor if len(freeNodes)<targetNumFreeNodes: freeNodes+=node if len(freeNodes)>=targetNumFreeNodes: break; else: print("Impossible to find enough free nodes to connect to.") exit() if len(freeNodes)!=targetNumFreeNodes: print("Warning: numfreeNodes not equal to target number but run out of things to try") return freeNodes # checks to see how many nodes have no neighbours. def numZeroNodes(self, g): numZeroNodes = 0 for node in g.nodes(): if sum(1 for _ in g.neighbors(node))==0: numZeroNodes+=1 return numZeroNodes def connectGraphWithConstrainedPolymer(self, g, points): ''' # we now traverse the graph generating a polymer for each edge in the network. # the nodes are the indices of the A and B points for each network, and the distance between # the points determines the minmum length of polymer, with extra length allowed by the curly factor. # We supply the growing list of points as points to avoid for subsequent chains. # Might make some impossible situations, so choose parameters carefully. ''' # create an array to store output outputXYZ = [] # specify number of crank moves to try to fold polymer in to zone numCrankMoves = 0 # loop through the edges in g and generate a self avoiding random polymer # that connects the specified points in the space for edge in g.edges(): pointA = points[edge[0]] pointB = points[edge[1]] # generate local points to avoid # copy externally supplied points l_pointsToAvoid = cp.copy(self.pointsToAvoid) # add existing output from other polymers providing they are more than minDist away from pointA and pointB for point in outputXYZ: if np.linalg.norm(point - pointA) > self.minDist and np.linalg.norm(point - pointB) > self.monomerMinDist: l_pointsToAvoid += [point] # add the other node points in the network providing they are far enough away from pointA and pointB for point in points: if np.linalg.norm(point - pointA) > self.minDist and np.linalg.norm(point - pointB) > self.monomerMinDist: l_pointsToAvoid += [point] # compute the number of vertices we will need between the two points. numPoints = int(self.curlyFactor * np.linalg.norm(pointA - pointB)/self.bondLength) polymerBB = self.CPPBBG.generateBuildingBlock( numPoints, pointA, pointB, self.monomerMinDist, self.bondLength, numCrankMoves, l_pointsToAvoid, visualiseEnvelope=self.visualiseEnvelope, envelopeList=self.envelopeList, angularRange=self.angularRange) # add the newly minted points to the list of output points. outputXYZ += [ point for point in polymerBB.blockXYZVals] return outputXYZ
class BranchedPolymerPackBBG(BBG): ''' Overloads the building block generator class, and makes use of the random polymer pack generator, to generate a set of interweaving polymer chains. The directors of the first generation of chains can be correlated within a specified deviation to a global director. The first generation of polymer chains can then be used as a seed points for branching polymer chains which have a specified density and minimum interior branch angle. Each of the subsequent unimers branches may then be used as a seed points for additional unimers up to a specified number of generations of branching. Every unimer that is generated will have a envelope which is a cone. The same shape cone will be used for all unimers - they are related molecules afterall - but the orientation will be selected based on the degree of correlation selected. Prior to computing each new unimer any of the over all set of points that are in the cone will be isolated as points to Avoid for that unimer. In this way no unimer thread should intersect any other. The self avoidance can be turned off. It turns out the polymers miss each other most the time anyway. Can live with the odd clash, if we're only making cartoons. A networkx graph of the polymer network is created whose nodes are the indices of the points in output xyzlist and whose edges are the points in that list which are connected by a bond. There are many additional parameters: the alpha and beta ranges for the internal angles of the polymer. the networkMinDist is the distance between the starting points of the polymer chains MonomerMinDist is as close as two chains can come. BondLength is the distance between successive unimers along the chain. Bondlength must be longer than minDist or the chain self rejects. (sorry). Minpoints per unimer is the smallest number of points allowed on a chain. typically the chain length is determined by the distance to the outer box, and then 50 % of the time the polymers are randomly truncated by a randomfactor between half and 1. MaxNumAttempts is a bailout counter for some while loops which repeat various unimer constructions. Oftimes the starting point is just in a bad position. Uggh. I'm bored. figure out the rest of the params yourself. They're pretty self explanatory. ''' def __init__(self, filename): BBG.__init__(self, filename) def initialiseParameters(self): # ensure parent initialisation takes place and core values are initialised BBG.initialiseParameters(self) self.particleName = self.getParam('particleName') self.RPPBBG = RPPBBG(self.paramFilename) self.VPCBBG = VPCBBG(self.paramFilename) if self.noLoadErrors == False: print( "Critical Parameters are undefined for branched polymer network object" ) sys.exit() def generateBuildingBlock(self, numGen1Unimers, x1, x2, y1, y2, z1, z2, networkMinDist, alpha1, alpha2, beta1, beta2, monomerMinDist, bondLength, minNumPointsUnimer, maxNumAttempts, selfAvoid, coneAngle, directorMaxPitch, globalDirector, numBranchGenerations, branchDensity, minBranchAngle, angularRange=['None'], pointsToAvoid=[], visualiseEnvelope=(0, 20), envelopeList=["None"], SpaceCurveTransform=True): numPoints = 0 # computed on the fly in this case self.networkGraph = nx.Graph() self.numGen1Unimers = numGen1Unimers self.x1 = x1 self.x2 = x2 self.y1 = y1 self.y2 = y2 self.z1 = z1 self.z2 = z2 self.networkMinDist = networkMinDist self.monomerMinDist = monomerMinDist self.alpha1 = alpha1 self.alpha2 = alpha2 self.beta1 = beta1 self.beta2 = beta2 self.bondLength = bondLength self.minNumPointsUnimer = minNumPointsUnimer self.maxNumAttempts = maxNumAttempts self.selfAvoid = selfAvoid self.coneAngle = coneAngle * np.pi / 180.0 self.directorMaxPitch = directorMaxPitch * np.pi / 180.0 self.globalDirector = globalDirector self.numBranchGenerations = numBranchGenerations self.branchDensity = branchDensity self.minBranchAngle = minBranchAngle * np.pi / 180.0 self.pointsToAvoid = pointsToAvoid self.visualiseEnvelope = visualiseEnvelope self.envelopeList = envelopeList + [ 'cuboid ' + str(x1) + ' ' + str(x2) + ' ' + str(y1) + ' ' + str(y2) + ' ' + str(z1) + ' ' + str(z2) ] self.angularRange = angularRange self.blockRefPoint = self.generateBuildingBlockRefPoint() self.SpaceCurveTransform = SpaceCurveTransform return BBG.generateBuildingBlock(self, numPoints, networkMinDist, envelopeList=self.envelopeList, visualiseEnvelope=visualiseEnvelope, pointsToAvoid=pointsToAvoid) def generateBuildingBlockDirector(self): return np.array([0.0, 0.0, 1.0]) def generateBuildingBlockRefPoint(self): return np.array([(self.x1 + self.x2) / 2.0, (self.y1 + self.y2) / 2.0, (self.z1 + self.z2) / 2.0]) def generateBuildingBlockNames(self): return [self.particleName] * self.numPoints # over load the final function to get a building block, so that the networkGraph can be passed back as well. def getBuildingBlock(self): # access function that returns a building block based on the backbone return (BuildingBlock(self.buildingBlockXYZ, self.blockNames, self.blockConnectors, self.blockRefPoint, self.blockDirectorHat), self.networkGraph) def generateBuildingBlockXYZ(self): # first pick n random points in the specified envelope within a cubic region of space. # These are the unimer start points. Leave a margin at the edge to try to eliminate dodgy scenarios startPoints = self.VPCBBG.generateBuildingBlock( self.numGen1Unimers, self.x1 + self.minNumPointsUnimer * self.bondLength, self.x2 - self.minNumPointsUnimer * self.bondLength, self.y1 + self.minNumPointsUnimer * self.bondLength, self.y2 - self.minNumPointsUnimer * self.bondLength, self.z1 + self.minNumPointsUnimer * self.bondLength, self.z2 - self.minNumPointsUnimer * self.bondLength, self.networkMinDist, envelopeList=self.envelopeList, pointsToAvoid=self.pointsToAvoid) if self.dumpInterimFiles == 1: fIO.saveXYZ(startPoints.blockXYZVals, 'C', 'pointsInCube.xyz') # for each point generate a direction relative to the globalDirector in the allowed pitch range. # azimuthally can choose anything, but elevation wise we are restrained to be within directorMaxPitch # angle of the globalDirector. Choose a direction that yields at least minNumPointsUnimer gen1UnimerDirectors = [ self.genDirector(point, self.minNumPointsUnimer, self.globalDirector, self.directorMaxPitch, self.maxNumAttempts) for point in startPoints.blockXYZVals ] # set up output arrays xyzPoints = [] UnimerIndices = [] unimersToBranchFrom = [] currentUnimer = 0 # Now generate the gen1 Unimers for point, director in zip(startPoints.blockXYZVals, gen1UnimerDirectors): print("Generating Unimer: ", currentUnimer + 1, " of ", self.numGen1Unimers) # generate the next list of xyz values if self.selfAvoid: # director[0] is the vector, director[1] is the number of points unimerXYZVals = self.generateUnimer( point, director[0], director[1], xyzPoints + self.pointsToAvoid, self.maxNumAttempts) else: unimerXYZVals = self.generateUnimer(point, director[0], director[1], self.pointsToAvoid, self.maxNumAttempts) # make a tuple to note the start and end index of the new unimer in the points array. # add that tuple to a list of indices UnimerIndices += [(len(xyzPoints), len(xyzPoints) + len(unimerXYZVals))] # add the xyz points of the unimer to the growing list of xyzValues xyzPoints += cp.copy(unimerXYZVals) # make a note of the index of the latest tuple pair added. # Each generation only adds to the last worms created unimersToBranchFrom += [len(UnimerIndices) - 1] currentUnimer += 1 print("First Generation Unimers Complete.") if self.dumpInterimFiles == 1: fIO.saveXYZ(xyzPoints, 'Ca', 'FirstGenUnimers.xyz') # loop through the number of generations we want to have for genNum in range(0, self.numBranchGenerations): print("Now creating unimers for generation ", genNum + 1, " of ", self.numBranchGenerations) # create a new generation of unimers that will branch off the specified unimers. # return a full list of all the points, the indices of each unimer in points # and a list of new unimers from which we can branch from on the next generation xyzPoints, UnimerIndices, unimersToBranchFrom = self.generateNextUnimerGeneration( xyzPoints, UnimerIndices, unimersToBranchFrom) # we have a list of tuples which contain the start and end indices of each unimer. # Add this information to the graph network to complete the graphs. # The nodes that connect the daughter and parent unimers together are already in there. self.addNodesAndEdgesToGraphFromUnimerIndices(UnimerIndices) return xyzPoints # Each entry in unimer indices indicates a connected set of nodes. # Add those nodes to the graph and then edges between them. def addNodesAndEdgesToGraphFromUnimerIndices(self, UnimerIndices): for segment in UnimerIndices: # add the first node of the segment if it is not there already if not self.networkGraph.has_node(segment[0]): self.networkGraph.add_node(segment[0]) # for each subsequent node, add it and connect it to the previous node in the segment for node in range(segment[0] + 1, segment[1]): if not self.networkGraph.has_node(node): self.networkGraph.add_node(node) # add the edge connecting the previous node to the current node self.networkGraph.add_edge(node - 1, node) # generates a director for a given point. Makes sure that there is space for at least N bond lengths # from the point to the edge of the box for that director. Also ensure the picked director is within # angle radians of gDirector. def genDirector(self, point, minNumPoints, gDirector, angle, maxNumAttempts, within=True): numPoints = 0 maxNumPoints = 0 numLoops = 0 # loop until we exceed the minimum number of points requested or we have tried 10 times while numPoints < minNumPoints and numLoops < maxNumAttempts: if numLoops > 0: print("Finding alternative director. Attempt: ", numLoops + 1, "of ", maxNumAttempts) if within: # for choosing first generation unimers (must be within angle of gDirector) theta, phi = coords.pickRandomPointOnUnitSphereInAngRange( np.pi / 2 - angle, np.pi / 2, -np.pi, np.pi) else: # for choosing nth generation unimers (must be between angle and 2 angle of gdirector) theta, phi = coords.pickRandomPointOnUnitSphereInAngRange( np.pi / 2 - 2 * angle, np.pi / 2 - angle, -np.pi, np.pi) # convert theta, phi pair to xyz vectors directorHat = coords.sphericalPolar2XYZ(np.array([1.0, theta, phi])) # rotate director to take into account orientation of globalDirector directorHatRot = coords.transformFromBlockFrameToLabFrame( gDirector, np.array([0.0, 0.0, 0.0]), 0.0, np.array([0.0, 0.0, 1.0]), np.array([0.0, 0.0, 0.0]), [directorHat])[0] # calculate distance along director vector to the outer edge of the box. dist2Box = self.dist2Box(point, directorHatRot) # compute number of points we can fit in there assuming straight line to edge of box numPoints = int(dist2Box / self.bondLength) # make a note of the direction that gives the largest number of points so far if numPoints > maxNumPoints: maxDirector = cp.copy(directorHatRot) maxNumPoints = numPoints if numPoints < minNumPoints: print("low num points") numLoops += 1 # if we have exited the loop then check to see if we have succeeded. # if we have failed then return the best director we encountered. if numPoints < minNumPoints: print("Warning: Director choice resulted in short polymer") numPoints = maxNumPoints directorHatRot = maxDirector return (directorHatRot, numPoints) def generateNextUnimerGeneration(self, points, indices, unimersToGrowFrom): # The points for the entire network are contained in flat list of points. # Each unimer is identified by a tuple of indices referring to the start and end points in the array. # The list of tuples is stored in indices. # Unimers to Grow from is a list of unimers i.e. their position in the index list, from which to grow # a new unimer. # create an array to keep track of the index pairs of each new unimer we add them. # This will form the list to grow from for the next generation. newUnimersToGrowFrom = [] # loop through the unimers to grow from. for unimerNum, unimer in enumerate(unimersToGrowFrom): print("Generating Unimer: ", unimerNum + 1, " of ", len(unimersToGrowFrom)) # get the indices of the current unimer from which we are going to branch startIndex = indices[unimer][0] endIndex = indices[unimer][1] length = endIndex - startIndex # compute how many branch points to add numBranchPoints = int(float(length) * self.branchDensity) # generate a list of indexes where the branches will happen seedPoints = [] for _ in range(0, numBranchPoints): newSeedPoint = rnd.randint( startIndex + 2, endIndex - 2 ) # don't use the first few points in a unimer - makes for crappy looking networks. while newSeedPoint in seedPoints: # loop until we don't pick the same index multiple times newSeedPoint = rnd.randint(startIndex + 2, endIndex - 2) seedPoints += [newSeedPoint] # loop through the selected seed points for seedPoint in seedPoints: # generate a director that is not within branch angle of the local # seed unimer director = self.genDirector(points[seedPoint], self.minNumPointsUnimer, points[seedPoint + 1] - points[seedPoint], self.minBranchAngle, self.maxNumAttempts, within=False) if self.selfAvoid: newUnimerXYZ = self.generateUnimer( points[seedPoint], director[0], director[1], points + self.pointsToAvoid, self.maxNumAttempts) else: newUnimerXYZ = self.generateUnimer(points[seedPoint], director[0], director[1], self.pointsToAvoid, self.maxNumAttempts) # Add the current new unimer's point indices to the list of unimers start and stop indices - we subtract 1 # because the seed point is already in the parent unimer. We are only adding the new points. indices += [(len(points), len(points) + len(newUnimerXYZ) - 1)] # At this point this is the only time in the program we know the index values of # both the parent and daughter unimers and where they are connected. # so add this information to the graph. # add the current seed point as a node if it isn't already there if not self.networkGraph.has_node(seedPoint): self.networkGraph.add_node(seedPoint) # add the start index of the most recent unimer added to the unimer index list if not self.networkGraph.has_node(indices[-1][0]): self.networkGraph.add_node(indices[-1][0]) # connect those two nodes in the graph self.networkGraph.add_edge(seedPoint, indices[-1][0]) # add the xyz points of the unimer to the growing list of xyzValues # only add the new points - don't add the seed point which would be redundant. points += cp.copy(newUnimerXYZ[1:]) # add the index number of the latest unimer to the newUnimersToGrowFrom list for the next gen # subtract 1 because it is zero based, where the length of the array is not. newUnimersToGrowFrom += [len(indices) - 1] return points, indices, newUnimersToGrowFrom # generates a single unimer which starts at point point, in direction of director # avoiding all the points in xyzList, and uses global params in the class to # define the unimer details. def generateUnimer(self, point, director, numPoints, pointsToAvoid, maxNumAttempts): # half the time, scale the number of points by a random factor between 0.5 and 1.0 # otherwise all the unimers will leave the box if rnd.uniform(0.0, 1.0) > 1.0: numPoints *= rnd.uniform(0.5, 1.0) # make the numPoints an integer numPoints = int(numPoints) # develop the frustum definition in the block frame of the unimer z1 = -self.bondLength r1 = self.bondLength z2 = numPoints * self.bondLength r2 = z2 * np.tan(self.coneAngle / 2.0) localEnvelopeList = [ "frustum " + str(z1) + ' ' + str(r1) + ' ' + str(z2) + ' ' + str(r2) ] if numPoints == 0: print("Zero Length Polymer") numPointsOutside = numPoints numLoops = 0 # loop until we create a unimer that satisfies constraints. Can set maxNumAttempts depending on how bothered we are about constraints. while numPointsOutside > 0 and numLoops < maxNumAttempts: if numLoops > 0: print( "Re-generating Unimer - exceeds global envelope. Attempt: ", numLoops + 1, "of ", maxNumAttempts) # generate the unimer in it's own block frame starting at origin. Don't worry about outer envelope or other points at this stage unimer = self.RPPBBG.generateBuildingBlock( numPoints, np.array([0.0, 0.0, 0.0]), self.alpha1, self.alpha2, self.beta1, self.beta2, self.monomerMinDist, self.bondLength, pointsToAvoid=[], envelopeList=localEnvelopeList, SpaceCurveTransform=self.SpaceCurveTransform) #visualiseEnvelope=(100000,200,'envelopeUnimer.xyz')) if self.dumpInterimFiles == 1: fIO.saveXYZ(unimer.blockXYZVals, 'Ca', 'preRotUnimer.xyz') # now rotate and translate the unimer xyzVals to their final point rotXYZVals = coords.transformFromBlockFrameToLabFrame( director, point, 0.0, np.array([0.0, 0.0, 1.0]), np.array([0.0, 0.0, 0.0]), unimer.blockXYZVals) # Now check that the unimer is entirely within the outer box, and any global specified user envelope, # Also make sure that the unimer is networkMinDist points away from existing pointsToAvoid. # Won't self check the unimer against itself only the points to avoid. Allows to have two minDists. # One for the monomer distance along the backbone, and one for the distance between unimers (2 * worm tube radius). # enables the unimer bondlength to be small which makes for smoother unimer chains. numPointsOutside = len( self.checkEnvelope(rotXYZVals, ignorePTA=False, pointsToAvoid=pointsToAvoid)) # increment loop counter numLoops += 1 return rotXYZVals # def choosePointsToAvoid(self, points, Z1, R1, Z2, R2): # # go through points and keep any points that are inside the frustum # outPoints = [] # for point in points: # if coords.checkPointInFrustum(point, Z1, R1, Z2, R2, 0): # outPoints += [ point ] # return outPoints def dist2Box(self, point, director): # Find the distance from the point to the outer box in the direction of director # compute the line parameter where the given line intersects each of the six planes of the box. paramVals = [] paramVals += [ cart.distanceOfLineToAPlane(point, director, np.array([self.x1, 0, 0]), np.array([1.0, 0.0, 0.0])) ] paramVals += [ cart.distanceOfLineToAPlane(point, director, np.array([self.x2, 0, 0]), np.array([1.0, 0.0, 0.0])) ] paramVals += [ cart.distanceOfLineToAPlane(point, director, np.array([0, self.y1, 0]), np.array([0.0, 1.0, 0.0])) ] paramVals += [ cart.distanceOfLineToAPlane(point, director, np.array([0, self.y2, 0]), np.array([0.0, 1.0, 0.0])) ] paramVals += [ cart.distanceOfLineToAPlane(point, director, np.array([0, 0, self.z1]), np.array([0.0, 0.0, 1.0])) ] paramVals += [ cart.distanceOfLineToAPlane(point, director, np.array([0, 0, self.z2]), np.array([0.0, 0.0, 1.0])) ] # choose the smallest +ve distance. (selects the plane in the director of director) # when the line is parallel with the plane we're not bothered about that plane. # when the line goes through the corner of the box or two of the planes, it's the same distance # so we don't care. Automatically handles these cases. return min([a for a in paramVals if a > 0.0])
PegXYZ - PEGStrand.blockXYZVals[0] for PegXYZ in PEGStrand.blockXYZVals ] retStrand.addAtoms((newXYZVals, PEGStrand.blockAtomNames)) retStrand.blockAtomNames = names[:] return retStrand if __name__ == "__main__": PolyGen = RPBBG('RandomPolymer.txt') SphereBBG = SPSBBG('SurfacePackSphere.txt') #PyramidBBG = VPSBPBBG('VolumePackPyramid.txt') CubeBBG = VPCBBG('VolumePackCuboid.txt') numPolymersPerSphere = 195 AtomicMinDist = 1.0 NumPEG = 60 NumHPMA = 113 bondLength = 1.5 FRadius1 = 20.0 FRadius2 = 2.0 FZ1 = 150.0 FZ2 = 10.0 BaseX = 4000.0 BaseY = 5000.0 BaseZ = 1000.0 Volume = BaseX * BaseY * BaseZ