def latticeDistribution(self, nx, ny, rho, xmin=(0.0, 0.0), xmax=(1.0, 1.0), rmin=None, rmax=None, nNodePerh=2.01, xlmin=1e30, xlmax=-1e30): dx = (xmax[0] - xmin[0]) / nx dy = (xmax[1] - xmin[1]) / ny hx = 1.0 / (nNodePerh * dx) hy = 1.0 / (nNodePerh * dy) H0 = SymTensor2d(hx, 0.0, 0.0, hy) x = [] y = [] m = [] H = [] for j in xrange(ny): for i in xrange(nx): if i * dx < xlmin or i * dx > xlmax: xx = xmin[0] + (i + 0.5) * dx yy = xmin[1] + (j + 0.5) * dy r = sqrt(xx * xx + yy * yy) m0 = dx * dy * rho(Vector2d(xx, yy)) if ((r >= rmin or rmin is None) and (r <= rmax or rmax is None)): x.append(xx) y.append(yy) m.append(m0) H.append(H0) dx = dx * 0.5 dy = dy * 0.5 xx = xlmin + 0.5 * dx while (xx <= xlmax): for j in range(ny * 2): yy = xmin[1] + (j + 0.5) * dy r = sqrt(xx * xx + yy * yy) m0 = dx * dy * rho(Vector2d(xx, yy)) if ((r >= rmin or rmin is None) and (r <= rmax or rmax is None)): x.append(xx) y.append(yy) m.append(m0) H.append(H0) xx += dx return x, y, m, H
def __init__( self, boundary, # Some object that has "xmin", "xmax", & "contains" dx, # Nominal linear resolution rho, # initial mass density: constant, list, or function nNodePerh=2.01, # desired nPerh for H tensors jitter=0.0, # (fraction of dx) any randomness to initial positions SPH=False, # Should we force round H tensors? ): assert dx > 0.0 assert nNodePerh > 0.0 self.nNodePerh = nNodePerh # Start by clipping a lattice. xmin, xmax = boundary.xmin, boundary.xmax nx = max(1, int((xmax.x - xmin.x) / dx + 0.5)) ny = max(1, int((xmax.y - xmin.y) / dx + 0.5)) self.x, self.y = [], [] for iy in xrange(ny): for ix in xrange(nx): posi = Vector2d( xmin.x + (ix + 0.5 + jitter * rangen.uniform(0, 1)) * dx, xmin.y + (iy + 0.5 + jitter * rangen.uniform(0, 1)) * dx) if boundary.contains(posi): self.x.append(posi.x) self.y.append(posi.y) n = len(self.x) assert len(self.y) == n # Density and mass. if type(rho) is float: self.rho = ConstantRho(rho) else: self.rho = rho # Mass per node. self.m = [self.rho(Vector2d(self.x[i], self.y[i])) for i in xrange(n)] # Set H. h0 = nNodePerh * dx H0 = SymTensor2d(1.0 / h0, 0.0, 0.0, 1.0 / h0) self.H = [H0] * len(self.x) # Have the base class break up the serial node distribution # for parallel cases. NodeGeneratorBase.__init__(self, True, self.x, self.y, self.m, self.H) return
def computeWeightedCentroids(self, tessellation): barycenters = self.computeBarycenters(tessellation) centroids = [] for icell, cell in enumerate(tessellation.cells): mass = 0.0 xc = 0.0 yc = 0.0 for ftmp in cell: if ftmp < 0: n0 = tessellation.faces[~ftmp][1] n1 = tessellation.faces[~ftmp][0] else: n0 = tessellation.faces[ftmp][0] n1 = tessellation.faces[ftmp][1] x0 = tessellation.nodes[2 * n0] y0 = tessellation.nodes[2 * n0 + 1] x1 = tessellation.nodes[2 * n1] y1 = tessellation.nodes[2 * n1 + 1] xb = barycenters[icell][0] yb = barycenters[icell][1] xt = (x0 + x1 + xb) / 3.0 yt = (y0 + y1 + yb) / 3.0 xe = (x0 + x1) / 2.0 ye = (y0 + y1) / 2.0 d = 0.5 * ((x0 - xb) * (y1 - yb) - (x1 - xb) * (y0 - yb)) * self.rhofunc(Vector2d(xe, ye)) mass += d xc += d * xt yc += d * yt xc /= mass yc /= mass centroids.append(xc) centroids.append(yc) return centroids
def computeMasses(self, tessellation): barycenters = self.computeBarycenters(tessellation) result = [] for icell, cell in enumerate(tessellation.cells): mass = 0.0 for ftmp in cell: if ftmp < 0: n0 = tessellation.faces[~ftmp][1] n1 = tessellation.faces[~ftmp][0] else: n0 = tessellation.faces[ftmp][0] n1 = tessellation.faces[ftmp][1] x0 = tessellation.nodes[2 * n0] y0 = tessellation.nodes[2 * n0 + 1] x1 = tessellation.nodes[2 * n1] y1 = tessellation.nodes[2 * n1 + 1] xb = barycenters[icell][0] yb = barycenters[icell][1] xt = (x0 + x1 + xb) / 3.0 yt = (y0 + y1 + yb) / 3.0 xe = (x0 + x1) / 2.0 ye = (y0 + y1) / 2.0 d = 0.5 * ((x0 - xb) * (y1 - yb) - (x1 - xb) * (y0 - yb)) * self.rhofunc(Vector2d(xe, ye)) mass += d result.append(mass) return result
def _string2Vector2d(x): return Vector2d(*tuple(x.split()))
def __init__(self, nr, densityProfileMethod, rmin = 0.0, rmax = 1.0, thetaMin = 0.0, thetaMax = pi, nNodePerh = 2.01, offset=None, m0ForMassMatching=None): assert nr > 0 assert rmin >= 0 assert rmin < rmax assert thetaMin < thetaMax assert thetaMin >= 0.0 and thetaMin <= 2.0*pi assert thetaMax >= 0.0 and thetaMax <= 2.0*pi assert nNodePerh > 0.0 assert offset is None or len(offset)==3 if offset is None: self.offset = Vector2d(0,0) else: self.offset = Vector2d(offset[0],offset[1]) self.nr = nr self.rmin = rmin self.rmax = rmax self.thetaMin = thetaMin self.thetaMax = thetaMax self.nNodePerh = nNodePerh self.xmin = Vector2d(-2.0*rmax,-2.0*rmax) self.xmax = Vector2d(2.0*rmax,2.0*rmax) # no reason to support a constant density method here, just use a regular lattice for that self.densityProfileMethod = densityProfileMethod # Determine how much total mass there is in the system. targetMass = self.integrateTotalMass(self.densityProfileMethod, rmin, rmax, thetaMin, thetaMax) #targetMass = self.integrateTotalMass(self.densityProfileMethod, # rmax) targetN = pi*(nr**2) self.m0 = targetMass/targetN self.vol = pi*(rmax**2) # what this means is this currently only supports creating a full sphere and then # cutting out the middle to rmin if rmin > 0 if m0ForMassMatching is None: self.rho0 = targetMass/self.vol else: self.m0 = m0ForMassMatching self.rho0 = targetN*self.m0/self.vol print "Found total mass = {0:3.3e} with rho0 = {1:3.3e}".format(targetMass,self.rho0) # compute kappa first # k = 3/(self.rho0*rmax**3) * targetMass/(4.0*pi) # print "Found kappa={0:3.3f}. Was that what you expected?".format(k) nlat = nr # create the unstretched lattice self.xl, self.yl, self.ml, self.Hl = \ self.latticeDistribution(nlat, self.rho0, self.m0, self.xmin, # (xmin, ymin, zmin) self.xmax, # (xmax, ymax, zmax) self.rmax, self.nNodePerh) self.rl = [] for i in xrange(len(self.xl)): self.rl.append(sqrt(self.xl[i]**2+self.yl[i]**2)) print "Sorting unstretched lattice... %d elements" % len(self.rl) multiSort(self.rl,self.xl,self.yl) self.x = [] self.y = [] self.m = [] self.H = [] nx = 2*nlat+1 eta = (self.xmax[0] - self.xmin[0])/nx print "Stretching lattice..." dr = eta * 0.01 # this will essentially be the error in the new dumb way r0p = 0 rp = 0 rn = 0 for i in xrange(1,len(self.rl)): #print "%d / %d" % (i,len(self.rl)) r0 = self.rl[i] if (abs(r0-r0p)/r0>1e-10): sol = r0**2*self.rho0/2.0 iter = int(10*rmax // dr) fn = 0 for j in xrange(iter+1): rj = dr*j rjj = dr*(j+1) fj = rj * densityProfileMethod(rj) fjj = rjj * densityProfileMethod(rjj) fn = fn + 0.5*(fj+fjj)*dr if (fn>=sol): rn = rj break r0p = r0 if (rn <= rmax and rn > rmin): self.x.append(self.xl[i] * rn/r0) self.y.append(self.yl[i] * rn/r0) self.m.append(self.ml[i]) self.H.append(self.Hl[i]) seededMass = sum(self.m) mAdj = targetMass / seededMass for i in xrange(len(self.m)): self.m[i] = self.m[i] * mAdj # Initialize the base class. If "serialInitialization" is True, this # is where the points are broken up between processors as well. serialInitialization = True NodeGeneratorBase.__init__(self, serialInitialization, self.x, self.y, self.m, self.H) return
def localMassDensity(self, i): loc = Vector2d(0,0) loc = self.localPosition(i) - self.offset return self.densityProfileMethod(loc.magnitude())
def refineNodes2d(gen, deta = 0.25): n = gen.localNumNodes() x = [] y = [] m = [] rho = [] vx = [] vy = [] eps = [] H = [] extras = {} for name in gen.extraFields: extras[name] = [] etas = [Vector2d(-1, -1) * deta, Vector2d(-1, 1) * deta, Vector2d( 1, -1) * deta, Vector2d( 1, 1) * deta] for i in xrange(n): ri = gen.localPosition(i) mi = gen.localMass(i) Hi = gen.localHtensor(i) Hinv = Hi.Inverse() eigen = Hinv.eigenVectors() R = rotationMatrix2d(eigen.eigenVectors.getColumn(0)) T = SymTensor2d(eigen.eigenValues.x, 0.0, 0.0, eigen.eigenValues.y) T.rotationalTransform(eigen.eigenVectors) T = T*R.Inverse() mj = 0.25*mi Hj = Hi*2.0 for eta in etas: rj = ri + T*eta x.append(rj.x) y.append(rj.y) m.append(mj) rho.append(gen.rho[i]) vx.append(gen.vx[i]) vy.append(gen.vy[i]) eps.append(gen.eps[i]) H.append(Hj) for name in gen.extraFields: extras[name].append(gen.__dict__[name][i]) gen.x = x gen.y = y gen.m = m gen.rho = rho gen.vx = vx gen.vy = vy gen.eps = eps gen.H = H for name in gen.extraFields: gen.__dict__[name] = extras[name] for f in ([gen.x, gen.y, gen.m, gen.vx, gen.vy, gen.eps, gen.H] + [gen.__dict__[x] for x in gen.extraFields]): assert len(f) == len(etas)*n return
def localMassDensity(self, i): return self.gen2d.rhofunc((Vector2d(self.x[i], self.y[i]) - self.center).magnitude())
def __init__(self, drCenter, drRatio, rho, rmin, rmax, startFromCenter = True, thetamin = 0.0, thetamax = 0.5*pi, phi = pi, ntheta = 1, center = (0.0, 0.0, 0.0), distributionType = "constantDTheta", # one of (constantDTheta, constantNTheta) aspectRatio = 1.0, # only for constantDTheta nNodePerh = 2.01, SPH = False, rejecter = None): assert thetamax <= pi self.gen2d = GenerateRatioSphere2d(drStart = drCenter, drRatio = drRatio, rho = rho, rmin = rmin, rmax = rmax, startFromCenter = startFromCenter, thetamin = thetamin, thetamax = thetamax, ntheta = ntheta, center = (0.0, 0.0), distributionType = distributionType, aspectRatio = aspectRatio, nNodePerh = nNodePerh, SPH = SPH) # The 2D class already split the nodes up between processors, but # we want to handle that ourselves. Distribute the full set of RZ # nodes to every process, then redecompose them below. self.x = mpi.allreduce(self.gen2d.x[:], mpi.SUM) self.y = mpi.allreduce(self.gen2d.y[:], mpi.SUM) self.m = mpi.allreduce(self.gen2d.m[:], mpi.SUM) self.H = mpi.allreduce(self.gen2d.H[:], mpi.SUM) n = len(self.x) self.z = [0.0]*n self.globalIDs = [0]*n # Convert the 2-D H tensors to 3-D, and correct the masses. for i in xrange(n): xi = self.x[i] yi = self.y[i] H2d = SymTensor2d(self.H[i]) H2dinv = H2d.Inverse() hxy0 = 0.5*(H2dinv.Trace()) dphi = CylindricalBoundary.angularSpacing(yi, hxy0, nNodePerh, 2.0) assert dphi > 0.0 nsegment = max(1, int(phi/dphi + 0.5)) dphi = phi/nsegment hz = dphi*yi*nNodePerh self.H[i] = SymTensor3d(H2d.xx, H2d.xy, 0.0, H2d.yx, H2d.yy, 0.0, 0.0, 0.0, 1.0/hz) if SPH: h0 = self.H[i].Determinant()**(1.0/3.0) self.H[-1] = SymTensor3d(h0, 0.0, 0.0, 0.0, h0, 0.0, 0.0, 0.0, h0) # Convert the mass to the full hoop mass, which will then be used in # generateCylDistributionFromRZ to compute the actual nodal masses. mi = self.m[i] circ = 2.0*pi*yi mhoop = mi*circ self.m[i] = mhoop assert len(self.m) == n assert len(self.H) == n # Duplicate the nodes from the xy-plane, creating rings of nodes about # the x-axis. We use a C++ helper method for the sake of speed. kernelExtent = 2.0 extras = [] xvec = self.vectorFromList(self.x, vector_of_double) yvec = self.vectorFromList(self.y, vector_of_double) zvec = self.vectorFromList(self.z, vector_of_double) mvec = self.vectorFromList(self.m, vector_of_double) Hvec = self.vectorFromList(self.H, vector_of_SymTensor3d) globalIDsvec = self.vectorFromList(self.globalIDs, vector_of_int) extrasVec = vector_of_vector_of_double() for extra in extras: extrasVec.append(self.vectorFromList(extra, vector_of_double)) generateCylDistributionFromRZ(xvec, yvec, zvec, mvec, Hvec, globalIDsvec, extrasVec, nNodePerh, kernelExtent, phi, mpi.rank, mpi.procs) self.x = [x + center[0] for x in xvec] self.y = [x + center[1] for x in yvec] self.z = [z + center[2] for z in zvec] self.m = list(mvec) self.H = [SymTensor3d(x) for x in Hvec] self.globalIDs = list(globalIDsvec) for i in xrange(len(extras)): extras[i] = list(extrasVec[i]) self.center = Vector2d(*center) # If the user provided a "rejecter", give it a pass # at the nodes. if rejecter: self.x, self.y, self.z, self.m, self.H = rejecter(self.x, self.y, self.z, self.m, self.H) # Initialize the base class. NodeGeneratorBase.__init__(self, False, self.x, self.y, self.z, self.m, self.H) return
def localMassDensity(self, i): return self.rho(Vector2d(self.x[i], self.y[i]))
def __init__(self, n, rho, boundary, holes=[], maxIterations=100, fracTol=1.0e-3, tessellationFileName=None, nNodePerh=2.01, offset=(0.0, 0.0), rejecter=None): assert n > 0 # Did we get passed a function or a constant for the density? if type(rho) == type(1.0): def rhofunc(posi): return rho else: rhofunc = rho self.rhofunc = rhofunc # Build a polytope PLC version of the boundary. plc = poly.polytope.PLC2d() plc_coords = poly.vector_of_double() edges = boundary.edges vertices = boundary.vertices() plc.facets.resize(len(edges)) for i, edge in enumerate(edges): plc.facets[i].append(edge.first) plc.facets[i].append(edge.second) assert len(plc.facets[i]) == 2 for p in vertices: plc_coords.append(p[0]) plc_coords.append(p[1]) assert len(plc_coords) == 2 * len(vertices) # Add any holes to the boundary PLC. plc.holes.resize(len(holes)) for ihole, hole in enumerate(holes): offlast = len(plc_coords) / 2 edges = hole.edges vertices = hole.vertices() plc.holes[ihole].resize(len(edges)) for i, edge in enumerate(edges): plc.holes[ihole][i].append(offlast + edge.first) plc.holes[ihole][i].append(offlast + edge.second) assert len(plc.holes[ihole][i]) == 2 for p in vertices: plc_coords.append(p[0]) plc_coords.append(p[1]) assert len(plc_coords) % 2 == 0 # Initialize the desired number of generators in the boundary using the Sobol sequence. generators = poly.vector_of_double() seed = 0 length = max(boundary.xmax.x - boundary.xmin.x, boundary.xmax.y - boundary.xmin.y) while len(generators) < 2 * n: [coords, seed] = i4_sobol(2, seed) p = boundary.xmin + length * Vector2d(coords[0], coords[1]) ihole = 0 use = boundary.contains(p, False) if use: while use and ihole < len(holes): use = not holes[ihole].contains(p, False) ihole += 1 if use: generators.append(p.x) generators.append(p.y) assert len(generators) == 2 * n # Iterate the points toward centroidal relaxation. self.tessellation = poly.polytope.Tessellation2d() tessellator = poly.polytope.BoostTessellator2d() iteration = 0 maxDelta = 2.0 * fracTol while iteration < maxIterations and maxDelta > fracTol: tessellator.tessellate(points=generators, PLCpoints=plc_coords, geometry=plc, mesh=self.tessellation) new_generators = self.computeWeightedCentroids(self.tessellation) assert len(new_generators) == len(generators) maxDelta = 0.0 for i in xrange(len(generators) / 2): deltai = sqrt((generators[2 * i] - new_generators[2 * i])**2 + (generators[2 * i + 1] - new_generators[2 * i + 1])**2) maxDelta = max(maxDelta, deltai / length) generators[2 * i] = 0.5 * (generators[2 * i] + new_generators[2 * i]) generators[2 * i + 1] = 0.5 * (generators[2 * i + 1] + new_generators[2 * i + 1]) iteration += 1 print "CentroidalGenerator2d: Iteration %i, maxDelta=%g" % ( iteration, maxDelta) # If requested, write out the final tessellation to a silo file. if tessellationFileName: poly.polytope.writeTessellation2d(mesh=self.tessellation, filePrefix=tessellationFileName, nodeFields=None, edgeFields=None, faceFields=None, cellFields=None, cycle=iteration) # Now we can fill out the usual Spheral generator info. assert len(self.tessellation.cells) == n self.x, self.y, self.m, self.H = [], [], [], [] centroids = self.computeWeightedCentroids(self.tessellation) masses = self.computeMasses(self.tessellation) areas = self.computeAreas(self.tessellation) assert len(centroids) == 2 * n assert len(masses) == n assert len(areas) == n for i in xrange(n): self.x.append(centroids[2 * i] + offset[0]) self.y.append(centroids[2 * i + 1] + offset[1]) self.m.append(masses[i]) hi = nNodePerh * sqrt(areas[i] / pi) assert hi > 0.0 self.H.append(SymTensor2d(1.0 / hi, 0.0, 0.0, 1.0 / hi)) assert len(self.x) == n assert len(self.y) == n assert len(self.m) == n assert len(self.H) == n # If the user provided a "rejecter", give it a pass # at the nodes. if rejecter: self.x, self.y, self.m, self.H = rejecter(self.x, self.y, self.m, self.H) # Have the base class break up the serial node distribution # for parallel cases. NodeGeneratorBase.__init__(self, True, self.x, self.y, self.m, self.H) return
def latticeCylindricalDistribution(self, nx, ny, rho, xmin=(0.0, 0.0), xmax=(1.0, 1.0), rmin=None, rmax=None, nNodePerh=2.01): k = 0 np = 0 deltar = rmax - rmin dx = (xmax[0] - xmin[0]) / nx dy = (xmax[1] - xmin[1]) / ny hx = 1.0 / (nNodePerh * dx) hy = 1.0 / (nNodePerh * dy) H0 = SymTensor2d(hx, 0.0, 0.0, hy) x = [] y = [] m = [] H = [] ml = [] Hl = [] xl = [] yl = [] xc = [] yc = [] mc = [] Hc = [] for j in xrange(ny): for i in xrange(nx): xx = xmin[0] + (i + 0.5) * dx yy = xmin[1] + (j + 0.5) * dy r = sqrt(xx * xx + yy * yy) m0 = dx * dy * rho(Vector2d(xx, yy)) if (r >= rmin * 0.8): xl.append(xx) yl.append(yy) ml.append(m0) Hl.append(H0) k = k + 1 if (r >= rmax): x.append(xx) y.append(yy) m.append(m0) H.append(H0) np = np + 1 # Start at the outermost radius, and work our way inward. theta = 2 * 3.14159 ri = rmax + 2.0 * nNodePerh / nx #import random #random.seed() while ri > 0: # Get the nominal delta r, delta theta, number of nodes, and mass per # node at this radius. rhoi = rho(Vector2d(ri, 0.0)) dr = sqrt(m0 / rhoi) arclength = theta * ri arcmass = arclength * dr * rhoi nTheta = max(1, int(arcmass / m0)) dTheta = theta / nTheta mi = arcmass / nTheta hi = nNodePerh * 0.5 * (dr + ri * dTheta) Hi = SymTensor2d(1.0 / hi, 0.0, 0.0, 1.0 / hi) # Now assign the nodes for this radius. for i in xrange(nTheta): thetai = (i + 0.5) * dTheta xc.append(ri * cos(thetai)) yc.append(ri * sin(thetai)) mc.append(mi) Hc.append(Hi) xi = ri * cos(thetai) yi = ri * sin(thetai) if (ri < rmin): x.append(xi) y.append(yi) m.append(mi) H.append(Hi) np = np + 1 elif (ri >= rmin): #eps = random.random() #func = ((ri-rmin)/deltar)**2 func = 1 - ri / (rmin - rmax) - rmax / (rmax - rmin) if (func > 1.0): func = 1.0 if (func < 0.0): func = 0.0 #if (eps <= func): #x.append(ri*cos(thetai)) #y.append(ri*sin(thetai)) #m.append(mi) #H.append(Hi) #else: minddr = nx mink = 2 * k for j in xrange(k): ddr = sqrt((xl[j] - xi)**2 + (yl[j] - yi)**2) if (ddr < minddr): minddr = ddr mink = j xi = xi + (xl[mink] - xi) * func yi = yi + (yl[mink] - yi) * func minddr = nx for j in xrange(np): ddr = sqrt((x[j] - xi)**2 + (y[j] - yi)**2) if (ddr < minddr): minddr = ddr if (minddr > (1.0 / hx) * 0.5): x.append(xi + (xl[mink] - xi) * func) y.append(yi + (yl[mink] - yi) * func) m.append(ml[mink]) H.append(Hl[mink]) # Decrement to the next radial bin inward. ri = max(0.0, ri - dr) return x, y, m, H
def __init__(self, nRadial, nTheta, rho, xmin=None, xmax=None, rmin=None, rmax=None, nNodePerh=2.01, theta=pi / 2.0, azimuthalOffsetFraction=0.0, SPH=False, rotation=0.0, offset=None, xminreject=None, xmaxreject=None, rreject=None, originreject=None, reversereject=False, relaxation=None, rejecter=None, xlmin=None, xlmax=None): assert nRadial > 0 assert nTheta > 0 assert nNodePerh > 0.0 assert offset is None or len(offset) == 2 assert ((xminreject is None and xmaxreject is None) or (len(xminreject) == 2 and len(xmaxreject) == 2)) assert ((rreject is None and originreject is None) or (rreject and len(originreject) == 2)) assert azimuthalOffsetFraction == 0.0 or theta == 2.0 * pi self.nRadial = nRadial self.nTheta = nTheta self.xmin = xmin self.xmax = xmax self.rmin = rmin self.rmax = rmax self.nNodePerh = nNodePerh self.theta = theta self.azimuthalOffsetFraction = azimuthalOffsetFraction self.xlmin = xlmin self.xlmax = xlmax # If the user provided a constant for rho, then use the constantRho # class to provide this value. if type(rho) == type(1.0): self.rho = ConstantRho(rho) else: self.rho = rho self.x, self.y, self.m, self.H = \ self.latticeDistribution(self.nRadial, # nx self.nTheta, # ny self.rho, self.xmin, self.xmax, self.rmin, self.rmax, self.nNodePerh, self.xlmin, self.xlmax) # If SPH has been specified, make sure the H tensors are round. if SPH: self.makeHround() # If requested, apply a rotation (in radians). if rotation: nhat = Vector2d(cos(rotation), -sin(rotation)) R = rotationMatrix2d(nhat) for i in xrange(len(self.x)): v = R * Vector2d(self.x[i], self.y[i]) self.x[i], self.y[i] = v.x, v.y self.H[i].rotationalTransform(R) # If requested, shift the nodes. if offset: for i in xrange(len(self.x)): self.x[i] += offset[0] self.y[i] += offset[1] # Did the user request to reject nodes based on a box? if xminreject: assert len(xminreject) == 2 and len(xmaxreject) == 2 xminreject = Vector2d(*xminreject) xmaxreject = Vector2d(*xmaxreject) x = [] y = [] m = [] H = [] for xi, yi, mi, Hi in zip(self.x, self.y, self.m, self.H): t = testPointInBox2d(Vector2d(xi, yi), xminreject, xmaxreject) if ((t and (not reversereject)) or ((not t) and reversereject)): x.append(xi) y.append(yi) m.append(mi) H.append(Hi) self.x = x self.y = y self.m = m self.H = H # Did the user request to reverse nodes based on a cylinder? if rreject: assert len(originreject) == 2 originreject = Vector2d(*originreject) x = [] y = [] m = [] H = [] for xi, yi, mi, Hi in zip(self.x, self.y, self.m, self.H): t = (Vector2d(xi, yi) - originreject).magnitude() <= rreject if ((t and (not reversereject)) or ((not t) and reversereject)): x.append(xi) y.append(yi) m.append(mi) H.append(Hi) self.x = x self.y = y self.m = m self.H = H # If the user provided a "rejecter", give it a pass # at the nodes. if rejecter: self.x, self.y, self.m, self.H = rejecter(self.x, self.y, self.m, self.H) # Have the base class break up the serial node distribution # for parallel cases. NodeGeneratorBase.__init__(self, True, self.x, self.y, self.m, self.H) # If requested, employ some relaxation on the NodeDistribution. if relaxation: relaxation(self) return
def __init__(self, dxSurface, xratio, dySurface, yratio, rho, xmin, xmax, nNodePerh=2.01, SPH=False, flipx=False, flipy=False): assert dxSurface > 0.0 assert xratio > 0.0 assert dySurface > 0.0 assert yratio > 0.0 assert xmin[0] < xmax[0] assert xmin[1] < xmax[1] assert nNodePerh > 0.0 # If the user provided a constant for rho, then use the constantRho # class to provide this value. if type(rho) == type(1.0): self.rhofunc = ConstantRho(rho) else: self.rhofunc = rho self.x, self.y, self.m, self.H, self.rho = [], [], [], [], [] # Decide the actual ratios we're going to use to arrive at an integer number of radial bins. def adjustRatio(drStart, drRatio, rmin, rmax): if abs(drRatio - 1.0) > 1e-4: neff = max( 1, int( log(1.0 - (rmax - rmin) * (1.0 - drRatio) / drStart) / log(drRatio) + 0.5)) drStart = (rmax - rmin) * (1.0 - drRatio) / (1.0 - drRatio**neff) else: neff = max(1, int((rmax - rmin) / drStart + 0.5)) drStart = (rmax - rmin) / neff return drStart, neff dxSurface, nxeff = adjustRatio(dxSurface, xratio, xmin[0], xmax[0]) dySurface, nyeff = adjustRatio(dySurface, yratio, xmin[1], xmax[1]) print "Adjusting initial spacing to (%g, %g) in order to create integer numbers of bins (%i, %i) to edges." % ( dxSurface, dySurface, nxeff, nyeff) def flipcoord(xi, x0, x1): return x0 + x1 - xi # Work our way in from the y surface. y1 = xmax[1] dy = dySurface while y1 > xmin[1] + 0.1 * dy: y0 = max(xmin[1], y1 - dy) yi = 0.5 * (y0 + y1) hy = nNodePerh * dy yi0 = y0 yi1 = y1 if flipy: yi = flipcoord(yi, xmin[1], xmax[1]) yi0 = flipcoord(yi0, xmin[1], xmax[1]) yi1 = flipcoord(yi1, xmin[1], xmax[1]) # Work our way in from the x surface. x1 = xmax[0] dx = dxSurface while x1 > xmin[0] + 0.1 * dx: x0 = max(xmin[0], x1 - dx) xi = 0.5 * (x0 + x1) hx = nNodePerh * dx xi0 = x0 xi1 = x1 if flipx: xi = flipcoord(xi, xmin[0], xmax[0]) xi0 = flipcoord(xi0, xmin[0], xmax[0]) xi1 = flipcoord(xi1, xmin[0], xmax[0]) self.x.append(xi) self.y.append(yi) rhoi, mi = computeRhoAndMass2d(Vector2d(xi0, yi0), Vector2d(xi1, yi0), Vector2d(xi1, yi1), Vector2d(xi0, yi1), Vector2d(xi, yi), self.rhofunc) self.m.append(mi) self.rho.append(rhoi) self.H.append(SymTensor2d(1.0 / hx, 0.0, 0.0, 1.0 / hy)) x1 = x0 dx *= xratio y1 = y0 dy *= yratio # # Make sure the total mass is what we intend it to be, by applying # # a multiplier to the particle masses. # M0 = 0.0 # for m in self.m: # M0 += m # assert M0 > 0.0 # M1 = (xmax[0] - xmin[0]) * (xmax[1] - xmin[1]) * rho # massCorrection = M1/M0 # for i in xrange(len(self.m)): # self.m[i] *= massCorrection # print "Applied a mass correction of %f to ensure total mass is %f." % (massCorrection, M1) # Have the base class break up the serial node distribution # for parallel cases. NodeGeneratorBase.__init__(self, True, self.x, self.y, self.m, self.H, self.rho) return
def localPosition(self, i): assert i >= 0 and i < len(self.x) assert len(self.x) == len(self.y) return Vector2d(self.x[i], self.y[i])
def localMassDensity(self, i): assert i >= 0 and i < len(self.H) return self.rhofunc(Vector2d(self.x[i], self.y[i]))
def __init__(self, drStart, drRatio, rho, rmin, rmax, startFromCenter = True, thetamin = 0.0, thetamax = 0.5*pi, ntheta = 1, center = (0.0, 0.0), distributionType = "constantDTheta", # one of (constantDTheta, constantNTheta) aspectRatio = 1.0, # only for constantDTheta nNodePerh = 2.01, SPH = False, rejecter = None, perturbFunc = None): assert drStart > 0.0 assert drRatio > 0.0 assert nNodePerh > 0.0 assert rmin >= 0.0 assert rmax > rmin assert thetamax > thetamin assert distributionType.lower() in ("constantdtheta", "constantntheta") self.center = center # Did we get passed a function or a constant for the density? if type(rho) == type(1.0): def rhofunc(posi): return rho else: rhofunc = rho self.rhofunc = rhofunc # Do we have a perturbation function? def zeroPerturbation(posi): return posi if not perturbFunc: perturbFunc = zeroPerturbation self.x, self.y, self.m, self.H = [], [], [], [] constantN = (distributionType.lower() == "constantntheta") Dtheta = thetamax - thetamin nthetamin = max(2, int(Dtheta/(0.5*pi) + 0.5)*2) # Decide the actual drStart we're going to use to arrive at an integer number of radial bins. if abs(drRatio - 1.0) > 1e-4: neff = max(1, int(log(1.0 - (rmax - rmin)*(1.0 - drRatio)/drStart)/log(drRatio) + 0.5)) drStart = (rmax - rmin)*(1.0 - drRatio)/(1.0 - drRatio**neff) else: neff = max(1, int((rmax - rmin)/drStart + 0.5)) drStart = (rmax - rmin)/neff print "Adjusting initial radial spacing to %g in order to create an integer radial number of bins %i." % (drStart, neff) # Step in radius (in or out) until we span the full radial range. dr = drStart for i in xrange(neff): if abs(drRatio - 1.0) > 1e-4: if startFromCenter: r0 = min(rmax, rmin + drStart*(1.0 - drRatio**i)/(1.0 - drRatio)) r1 = min(rmax, rmin + drStart*(1.0 - drRatio**(i + 1))/(1.0 - drRatio)) else: r0 = max(rmin, rmax - drStart*(1.0 - drRatio**(i + 1))/(1.0 - drRatio)) r1 = max(rmin, rmax - drStart*(1.0 - drRatio**i)/(1.0 - drRatio)) else: r0 = min(rmax, rmin + i*drStart) r1 = min(rmax, rmin + (i + 1)*drStart) dr = r1 - r0 ri = 0.5*(r0 + r1) li = Dtheta*ri if constantN: ntheta = ntheta else: ntheta = max(nthetamin, int(li/dr*aspectRatio)) dtheta = Dtheta/ntheta hr = nNodePerh * dr ha = nNodePerh * ri*dtheta for j in xrange(ntheta): theta0 = thetamin + j*dtheta theta1 = thetamin + (j + 1)*dtheta pos0 = perturbFunc(Vector2d(r0*cos(theta0), r0*sin(theta0))) pos1 = perturbFunc(Vector2d(r1*cos(theta0), r1*sin(theta0))) pos2 = perturbFunc(Vector2d(r1*cos(theta1), r1*sin(theta1))) pos3 = perturbFunc(Vector2d(r0*cos(theta1), r0*sin(theta1))) areai = 0.5*((pos1 - pos0).cross(pos2 - pos0).z + (pos2 - pos0).cross(pos3 - pos0).z) posi = 0.25*(pos0 + pos1 + pos2 + pos3) mi = areai*self.rhofunc(posi) xi = posi.x yi = posi.y self.x.append(xi + center[0]) self.y.append(yi + center[1]) self.m.append(mi) if SPH: hi = sqrt(hr*ha) self.H.append(SymTensor2d(1.0/hi, 0.0, 0.0, 1.0/hi)) else: self.H.append(SymTensor2d(1.0/hr, 0.0, 0.0, 1.0/ha)) runit = Vector2d(xi, yi).unitVector() T = rotationMatrix2d(runit).Transpose() self.H[-1].rotationalTransform(T) # # Do a numerical integral to get the expected total mass. # class integfunc(ScalarFunctor): # def __init__(self, rho, Dtheta): # ScalarFunctor.__init__(self) # self.rho = rho # self.Dtheta = Dtheta # return # def __call__(self, ri): # return Dtheta*ri*self.rho(ri) # M1 = simpsonsIntegrationDouble(integfunc(rhofunc, Dtheta), rmin, rmax, 10000) # # Make sure the total mass is what we intend it to be, by applying # # a multiplier to the particle masses. # M0 = sum(self.m) # assert M0 > 0.0 # massCorrection = M1/M0 # for i in xrange(len(self.m)): # self.m[i] *= massCorrection # print "Applied a mass correction of %f to ensure total mass is %f." % (massCorrection, M1) # If the user provided a "rejecter", give it a pass # at the nodes. if rejecter: self.x, self.y, self.m, self.H = rejecter(self.x, self.y, self.m, self.H) # Have the base class break up the serial node distribution # for parallel cases. NodeGeneratorBase.__init__(self, True, self.x, self.y, self.m, self.H) return