Example #1
0
	def updateMesh(self, ctrs, fd, frame_orig, pfix = None, n_iter = 20):
		deltat = 0.1
		xmin, ymin, xmax, ymax = self.bbox
		
		if pfix is not None:
			self.p = ml.setdiff_rows(self.p, pfix)
			pfix = ml.unique_rows(pfix)
			nfix = pfix.shape[0]
		else:
			nfix = 0
	
		N = self.p.shape[0]                                   # Number of points N
		pold = float('inf')                              # For first iteration
	
		################################################################################
		#Mesh updates
		################################################################################
		
		#self.delaunay = spspatial.Delaunay(self.p)
		#self.t = self.delaunay.vertices       # List of triangles
		for ii in range(n_iter):
			dist = lambda p1, p2: np.sqrt(((p1-p2)**2).sum(1))
			if (dist(self.p, pold)/self.h0).max() > self.ttol:          # Any large movement?
				pold = self.p.copy()                          # Save current positions
				pmid = self.p[self.t].sum(1)/3                     # Compute centroids
				self.t = self.t[fd(pmid) < -self.geps]                  # Keep interior triangles
				bars = np.vstack((self.t[:, [0,1]],
									self.t[:, [1,2]],
									self.t[:, [2,0]]))          # Interior bars duplicated
				bars.sort(axis=1)
				bars = ml.unique_rows(bars)              # Bars as node pairs
	
			barvec = self.p[bars[:,0]] - self.p[bars[:,1]]         # List of bar vectors
			L = np.sqrt((barvec**2).sum(1))              # L = Bar lengths
			hbars = self.fh(self.p[bars].sum(1)/2)
			L0 = 1.4*self.h0*np.ones_like(L);
		
			#F = self.k*(L0-L)
			#F[F<0] = 0                         # Bar forces (scalars)
			F = self.F(L) 
			Fvec = F[:,None]/L[:,None].dot([[1,1]])*barvec # Bar forces (x,y components)
			Ftot = ml.dense(bars[:,[0,0,1,1]],
											np.repeat([[0,1,0,1]], len(F), axis=0),
											np.hstack((Fvec, -Fvec)),
											shape=(N, 2))
			Ftot[:nfix] = 0                              # Force = 0 at fixed points
			#self.p += self.deltat*Ftot                             # Update node positions
			self.p += deltat*Ftot                             # Update node positions
		
			d = fd(self.p); ix = d>0                          # Find points outside (d>0)
			ddeps = 1e-1
			for idx in range(10):
				if ix.any():
					dgradx = (fd(self.p[ix]+[ddeps,0])-d[ix])/ddeps # Numerical
					dgrady = (fd(self.p[ix]+[0,ddeps])-d[ix])/ddeps # gradient
					dgrad2 = dgradx**2 + dgrady**2
					self.p[ix] -= (d[ix]*np.vstack((dgradx, dgrady))/dgrad2).T # Project	

		self.bars = bars
		self.L = L 
Example #2
0
def distmesh2d(fd, fh, h0, bbox, pfix=None, fig='gcf'):
    """
    distmesh2d: 2-D Mesh Generator using Distance Functions.

    Usage
    -----
    >>> p, t = distmesh2d(fd, fh, h0, bbox, pfix)

    Parameters
    ----------
    fd:        Distance function d(x,y)
    fh:        Scaled edge length function h(x,y)
    h0:        Initial edge length
    bbox:      Bounding box, (xmin, ymin, xmax, ymax)
    pfix:      Fixed node positions, shape (nfix, 2)
    fig:       Figure to use for plotting, or None to disable plotting.

    Returns
    -------
    p:         Node positions (Nx2)
    t:         Triangle indices (NTx3)

    Example: (Uniform Mesh on Unit Circle)
    >>> fd = lambda p: sqrt((p**2).sum(1))-1.0
    >>> p, t = distmesh2d(fd, huniform, 2, (-1,-1,1,1))

    Example: (Rectangle with circular hole, refined at circle boundary)
    >>> fd = lambda p: ddiff(drectangle(p,-1,1,-1,1), dcircle(p,0,0,0.5))
    >>> fh = lambda p: 0.05+0.3*dcircle(p,0,0,0.5)
    >>> p, t = distmesh2d(fd, fh, 0.05, (-1,-1,1,1),
                          [(-1,-1), (-1,1), (1,-1), (1,1)])

    Example: (Polygon)
    >>> pv=[(-0.4, -0.5), (0.4, -0.2), (0.4, -0.7), (1.5, -0.4), (0.9, 0.1),
            (1.6, 0.8), (0.5, 0.5), (0.2, 1.0), (0.1, 0.4), (-0.7, 0.7),
            (-0.4, -0.5)]
    >>> fd = lambda p: dpoly(p, pv)
    >>> p, t = distmesh2d(fd, huniform, 0.1, (-1,-1, 2,1), pv)

    Example: (Ellipse)
    >>> fd = lambda p: p[:,0]**2/2**2 + p[:,1]**2/1**2 - 1
    >>> p, t = dm.distmesh2d(fd, dm.huniform, 0.2, (-2,-1, 2,1))

    Example: (Square, with size function point and line sources)
    >>> fd = lambda p: dm.drectangle(p,0,1,0,1)
    >>> fh = lambda p: np.minimum(np.minimum(
            0.01+0.3*abs(dm.dcircle(p,0,0,0)),
            0.025+0.3*abs(dm.dpoly(p,[(0.3,0.7),(0.7,0.5)]))), 0.15)
    >>> p, t = dm.distmesh2d(fd, fh, 0.01, (0,0,1,1), [(0,0),(1,0),(0,1),(1,1)])

    Example: (NACA0012 airfoil)
    >>> hlead=0.01; htrail=0.04; hmax=2; circx=2; circr=4
    >>> a=.12/.2*np.array([0.2969,-0.1260,-0.3516,0.2843,-0.1036])
    >>> a0=a[0]; a1=np.hstack((a[5:0:-1], 0.0))
    >>> fd = lambda p: dm.ddiff(
        dm.dcircle(p,circx,0,circr),
        (abs(p[:,1])-np.polyval(a1, p[:,0]))**2-a0**2*p[:,0])
    >>> fh = lambda p: np.minimum(np.minimum(
            hlead+0.3*dm.dcircle(p,0,0,0),
            htrail+0.3*dm.dcircle(p,1,0,0)), hmax)

    >>> fixx = 1.0-htrail*np.cumsum(1.3**np.arange(5))
    >>> fixy = a0*np.sqrt(fixx)+np.polyval(a1, fixx)
    >>> fix = np.vstack((
            np.array([(circx-circr,0),(circx+circr,0),
                      (circx,-circr),(circx,circr),
                      (0,0),(1,0)]),
            np.vstack((fixx, fixy)).T,
            np.vstack((fixx, -fixy)).T))
    >>> box = (circx-circr,-circr, circx+circr,circr)
    >>> h0 = min(hlead, htrail, hmax)
    >>> p, t = dm.distmesh2d(fd, fh, h0, box, fix)
    """

    if fig == 'gcf':
        import matplotlib.pyplot as plt
        fig = plt.gcf()

    dptol=.001; ttol=.1; Fscale=1.2; deltat=.2; geps=.001*h0;
    deps=np.sqrt(np.finfo(np.double).eps)*h0;
    densityctrlfreq=30;

    # Extract bounding box
    xmin, ymin, xmax, ymax = bbox
    if pfix is not None:
        pfix = np.array(pfix, dtype='d')

    # 0. Prepare a figure.
    if fig is not None:
        from distmesh.plotting import SimplexCollection
        fig.clf()
        ax = fig.gca()
        c = SimplexCollection()
        ax.add_collection(c)
        ax.set_xlim(xmin, xmax)
        ax.set_ylim(ymin, ymax)
        ax.set_aspect('equal')
        ax.set_axis_off()
        fig.canvas.draw()

    # 1. Create initial distribution in bounding box (equilateral triangles)
    x, y = np.mgrid[xmin:(xmax+h0):h0,
                    ymin:(ymax+h0*np.sqrt(3)/2):h0*np.sqrt(3)/2]
    x[:, 1::2] += h0/2                               # Shift even rows
    p = np.vstack((x.flat, y.flat)).T                # List of node coordinates

    # 2. Remove points outside the region, apply the rejection method
    p = p[fd(p)<geps]                                # Keep only d<0 points
    r0 = 1/fh(p)**2                                  # Probability to keep point
    p = p[np.random.random(p.shape[0])<r0/r0.max()]  # Rejection method
    if pfix is not None:
        p = ml.setdiff_rows(p, pfix)                 # Remove duplicated nodes
        pfix = ml.unique_rows(pfix); nfix = pfix.shape[0]
        p = np.vstack((pfix, p))                     # Prepend fix points
    else:
        nfix = 0
    N = p.shape[0]                                   # Number of points N

    count = 0
    pold = float('inf')                              # For first iteration

    while True:
        count += 1

        # 3. Retriangulation by the Delaunay algorithm
        dist = lambda p1, p2: np.sqrt(((p1-p2)**2).sum(1))
        if (dist(p, pold)/h0).max() > ttol:          # Any large movement?
            pold = p.copy()                          # Save current positions
            t = spspatial.Delaunay(p).vertices       # List of triangles
            pmid = p[t].sum(1)/3                     # Compute centroids
            t = t[fd(pmid) < -geps]                  # Keep interior triangles
            # 4. Describe each bar by a unique pair of nodes
            bars = np.vstack((t[:, [0,1]],
                              t[:, [1,2]],
                              t[:, [2,0]]))          # Interior bars duplicated
            bars.sort(axis=1)
            bars = ml.unique_rows(bars)              # Bars as node pairs
            # 5. Graphical output of the current mesh
            if fig is not None:
                c.set_simplices((p, t))
                fig.canvas.draw()

        # 6. Move mesh points based on bar lengths L and forces F
        barvec = p[bars[:,0]] - p[bars[:,1]]         # List of bar vectors
        L = np.sqrt((barvec**2).sum(1))              # L = Bar lengths
        hbars = fh(p[bars].sum(1)/2)
        L0 = (hbars*Fscale
              *np.sqrt((L**2).sum()/(hbars**2).sum()))  # L0 = Desired lengths

        # Density control - remove points that are too close
        if (count % densityctrlfreq) == 0 and (L0 > 2*L).any():
            ixdel = np.setdiff1d(bars[L0 > 2*L].reshape(-1), np.arange(nfix))
            p = p[np.setdiff1d(np.arange(N), ixdel)]
            N = p.shape[0]; pold = float('inf')
            continue

        F = L0-L; F[F<0] = 0                         # Bar forces (scalars)
        Fvec = F[:,None]/L[:,None].dot([[1,1]])*barvec # Bar forces (x,y components)
        Ftot = ml.dense(bars[:,[0,0,1,1]],
                        np.repeat([[0,1,0,1]], len(F), axis=0),
                        np.hstack((Fvec, -Fvec)),
                        shape=(N, 2))
        Ftot[:nfix] = 0                              # Force = 0 at fixed points
        p += deltat*Ftot                             # Update node positions

        # 7. Bring outside points back to the boundary
        d = fd(p); ix = d>0                          # Find points outside (d>0)
        if ix.any():
            dgradx = (fd(p[ix]+[deps,0])-d[ix])/deps # Numerical
            dgrady = (fd(p[ix]+[0,deps])-d[ix])/deps # gradient
            dgrad2 = dgradx**2 + dgrady**2
            p[ix] -= (d[ix]*np.vstack((dgradx, dgrady))/dgrad2).T # Project

        # 8. Termination criterion: All interior nodes move less than dptol (scaled)
        if (np.sqrt((deltat*Ftot[d<-geps]**2).sum(1))/h0).max() < dptol:
            break

    # Clean up and plot final mesh
    p, t = dmutils.fixmesh(p, t)

    if fig is not None:
        c.set_simplices((p, t))
        fig.canvas.draw()

    return p, t
Example #3
0
    def createMesh(self, ctrs, fd, frame, plot=False):
        self.frame = frame
        pfix = None

        # Extract bounding box
        xmin, ymin, xmax, ymax = self.bbox
        if pfix is not None:
            pfix = np.array(pfix, dtype='d')

        #1. Set up initial points
        x, y = np.mgrid[xmin:(xmax + self.h0):self.h0,
                        ymin:(ymax + self.h0 * np.sqrt(3) / 2):self.h0 *
                        np.sqrt(3) / 2]
        x[:, 1::2] += self.h0 / 2  # Shift even rows
        p = np.vstack((x.flat, y.flat)).T  # List of node coordinates

        # 2. Remove points outside the region, apply the rejection method
        a = fd(p)
        p = p[np.where(a < self.geps)]  # Keep only d<0 points
        r0 = 1 / self.fh(p)**2  # Probability to keep point
        p = p[np.random.random(p.shape[0]) < r0 / r0.max()]  # Rejection method
        if pfix is not None:
            p = ml.setdiff_rows(p, pfix)  # Remove duplicated nodes
            pfix = ml.unique_rows(pfix)
            nfix = pfix.shape[0]
            p = np.vstack((pfix, p))  # Prepend fix points
        else:
            nfix = 0
        N = p.shape[0]  # Number of points N
        self.N = N

        count = 0
        pold = float('inf')  # For first iteration

        ################################################################################
        #Mesh creation
        ################################################################################

        while count < self.maxiter:
            print 'DistMesh create count: %d/%d' % (count, self.maxiter)
            count += 1
            # 3. Retriangulation by the Delaunay algorithm
            dist = lambda p1, p2: np.sqrt(((p1 - p2)**2).sum(1))
            if (dist(p, pold) /
                    self.h0).max() > self.ttol:  # Any large movement?
                pold = p.copy()  # Save current positions
                self.delaunay = spspatial.Delaunay(p)
                t = self.delaunay.vertices  # List of triangles
                pmid = p[t].sum(1) / 3  # Compute centroids
                t = t[fd(pmid) < -self.geps]  # Keep interior triangles
                # 4. Describe each bar by a unique pair of nodes
                bars = np.vstack((t[:, [0, 1]], t[:, [1, 2]],
                                  t[:, [2, 0]]))  # Interior bars duplicated
                bars.sort(axis=1)
                bars = ml.unique_rows(bars)  # Bars as node pairs
                #Plot
                fc = frame.copy()
                if frame is not None:
                    drawGrid(fc, p, bars)
                    if plot:
                        cv2.imshow('Initial mesh', fc)
                        k = cv2.waitKey(30) & 0xff
                        if k == 27:
                            break

            # 6. Move mesh points based on bar lengths L and forces F
            barvec = p[bars[:, 0]] - p[bars[:, 1]]  # List of bar vectors
            L = np.sqrt((barvec**2).sum(1))  # L = Bar lengths
            hbars = self.fh(p[bars].sum(1) / 2)
            L0 = 1.5 * self.h0 * np.ones_like(L)
            #(hbars*Fscale
            #*np.sqrt((L**2).sum()/(hbars**2).sum()))  # L0 = Desired lengths

            F = self.k * (L0 - L)
            F[F <
              0] = 0  #F[F<0]*.5#0                         # Bar forces (scalars)
            Fvec = F[:, None] / L[:, None].dot(
                [[1, 1]]) * barvec  # Bar forces (x,y components)
            Ftot = ml.dense(bars[:, [0, 0, 1, 1]],
                            np.repeat([[0, 1, 0, 1]], len(F), axis=0),
                            np.hstack((Fvec, -Fvec)),
                            shape=(N, 2))
            Ftot[:nfix] = 0  # Force = 0 at fixed points
            p += self.deltat * Ftot  # Update node positions

            # 7. Bring outside points back to the boundary
            d = fd(p)
            ix = d > 0  # Find points outside (d>0)
            ddeps = 1e-1
            for idx in range(10):
                if ix.any():
                    dgradx = (fd(p[ix] + [ddeps, 0]) -
                              d[ix]) / ddeps  # Numerical
                    dgrady = (fd(p[ix] + [0, ddeps]) -
                              d[ix]) / ddeps  # gradient
                    dgrad2 = dgradx**2 + dgrady**2
                    p[ix] -= (d[ix] * np.vstack(
                        (dgradx, dgrady)) / dgrad2).T  # Project

            # 8. Termination criterion: All interior nodes move less than dptol (scaled)
            if (np.sqrt((self.deltat * Ftot[d < -self.geps]**2).sum(1)) /
                    self.h0).max() < self.dptol:
                break

        self.p = p
        self.t = t
        self.bars = bars
        self.L = L
Example #4
0
    def updateMesh(self, ctrs, fd, frame_orig, pfix=None, n_iter=20):
        deltat = 0.1
        xmin, ymin, xmax, ymax = self.bbox

        if pfix is not None:
            self.p = ml.setdiff_rows(self.p, pfix)
            pfix = ml.unique_rows(pfix)
            nfix = pfix.shape[0]
        else:
            nfix = 0

        N = self.p.shape[0]  # Number of points N
        pold = float('inf')  # For first iteration

        ################################################################################
        #Mesh updates
        ################################################################################

        #self.delaunay = spspatial.Delaunay(self.p)
        #self.t = self.delaunay.vertices       # List of triangles
        for ii in range(n_iter):
            dist = lambda p1, p2: np.sqrt(((p1 - p2)**2).sum(1))
            if (dist(self.p, pold) /
                    self.h0).max() > self.ttol:  # Any large movement?
                pold = self.p.copy()  # Save current positions
                pmid = self.p[self.t].sum(1) / 3  # Compute centroids
                self.t = self.t[fd(pmid) <
                                -self.geps]  # Keep interior triangles
                bars = np.vstack((self.t[:, [0, 1]], self.t[:, [1, 2]],
                                  self.t[:,
                                         [2, 0]]))  # Interior bars duplicated
                bars.sort(axis=1)
                bars = ml.unique_rows(bars)  # Bars as node pairs

            barvec = self.p[bars[:,
                                 0]] - self.p[bars[:,
                                                   1]]  # List of bar vectors
            L = np.sqrt((barvec**2).sum(1))  # L = Bar lengths
            hbars = self.fh(self.p[bars].sum(1) / 2)
            L0 = 1.4 * self.h0 * np.ones_like(L)

            #F = self.k*(L0-L)
            #F[F<0] = 0                         # Bar forces (scalars)
            F = self.F(L)
            Fvec = F[:, None] / L[:, None].dot(
                [[1, 1]]) * barvec  # Bar forces (x,y components)
            Ftot = ml.dense(bars[:, [0, 0, 1, 1]],
                            np.repeat([[0, 1, 0, 1]], len(F), axis=0),
                            np.hstack((Fvec, -Fvec)),
                            shape=(N, 2))
            Ftot[:nfix] = 0  # Force = 0 at fixed points
            #self.p += self.deltat*Ftot                             # Update node positions
            self.p += deltat * Ftot  # Update node positions

            d = fd(self.p)
            ix = d > 0  # Find points outside (d>0)
            ddeps = 1e-1
            for idx in range(10):
                if ix.any():
                    dgradx = (fd(self.p[ix] + [ddeps, 0]) -
                              d[ix]) / ddeps  # Numerical
                    dgrady = (fd(self.p[ix] + [0, ddeps]) -
                              d[ix]) / ddeps  # gradient
                    dgrad2 = dgradx**2 + dgrady**2
                    self.p[ix] -= (d[ix] * np.vstack(
                        (dgradx, dgrady)) / dgrad2).T  # Project

        self.bars = bars
        self.L = L
Example #5
0
	def createMesh(self, ctrs, fd, frame, plot = False):
		self.frame = frame 
		pfix = None 
	
		# Extract bounding box
		xmin, ymin, xmax, ymax = self.bbox
		if pfix is not None:
			pfix = np.array(pfix, dtype='d')
		
		#1. Set up initial points 
		x, y = np.mgrid[xmin:(xmax+self.h0):self.h0,
										ymin:(ymax+self.h0*np.sqrt(3)/2):self.h0*np.sqrt(3)/2]
		x[:, 1::2] += self.h0/2                               # Shift even rows
		p = np.vstack((x.flat, y.flat)).T                # List of node coordinates
		
		# 2. Remove points outside the region, apply the rejection method
		a = fd(p)
		p = p[np.where(a<self.geps)]                          # Keep only d<0 points
		r0 = 1/self.fh(p)**2                                  # Probability to keep point
		p = p[np.random.random(p.shape[0])<r0/r0.max()]  # Rejection method
		if pfix is not None:
			p = ml.setdiff_rows(p, pfix)                 # Remove duplicated nodes
			pfix = ml.unique_rows(pfix); nfix = pfix.shape[0]
			p = np.vstack((pfix, p))                     # Prepend fix points
		else:
			nfix = 0
		N = p.shape[0]                                   # Number of points N
		self.N = N
		
		count = 0
		pold = float('inf')                              # For first iteration
		
		################################################################################
		#Mesh creation
		################################################################################
		
		while count < self.maxiter: 
			#print count 
			count += 1
			# 3. Retriangulation by the Delaunay algorithm
			dist = lambda p1, p2: np.sqrt(((p1-p2)**2).sum(1))
			if (dist(p, pold)/self.h0).max() > self.ttol:          # Any large movement?
				pold = p.copy()                          # Save current positions
				self.delaunay = spspatial.Delaunay(p)
				t = self.delaunay.vertices       # List of triangles
				pmid = p[t].sum(1)/3                     # Compute centroids
				t = t[fd(pmid) < -self.geps]                  # Keep interior triangles
				# 4. Describe each bar by a unique pair of nodes
				bars = np.vstack((t[:, [0,1]],
									t[:, [1,2]],
									t[:, [2,0]]))          # Interior bars duplicated
				bars.sort(axis=1)
				bars = ml.unique_rows(bars)              # Bars as node pairs
				#Plot
				fc = frame.copy()
				if frame is not None:
					drawGrid(fc, p, bars)
					if plot:
						cv2.imshow('Initial mesh',fc)
						k = cv2.waitKey(30) & 0xff
						if k == 27:
							break
		
			# 6. Move mesh points based on bar lengths L and forces F
			barvec = p[bars[:,0]] - p[bars[:,1]]         # List of bar vectors
			L = np.sqrt((barvec**2).sum(1))              # L = Bar lengths
			hbars = self.fh(p[bars].sum(1)/2)
			L0 = 1.5*self.h0*np.ones_like(L); #(hbars*Fscale
						#*np.sqrt((L**2).sum()/(hbars**2).sum()))  # L0 = Desired lengths
		
			F = self.k*(L0-L)
			F[F<0] = 0#F[F<0]*.5#0                         # Bar forces (scalars)
			Fvec = F[:,None]/L[:,None].dot([[1,1]])*barvec # Bar forces (x,y components)
			Ftot = ml.dense(bars[:,[0,0,1,1]],
											np.repeat([[0,1,0,1]], len(F), axis=0),
											np.hstack((Fvec, -Fvec)),
											shape=(N, 2))
			Ftot[:nfix] = 0                              # Force = 0 at fixed points
			p += self.deltat*Ftot                             # Update node positions
		
			# 7. Bring outside points back to the boundary
			d = fd(p); ix = d>0                          # Find points outside (d>0)
			ddeps = 1e-1
			for idx in range(10):
				if ix.any():
					dgradx = (fd(p[ix]+[ddeps,0])-d[ix])/ddeps # Numerical
					dgrady = (fd(p[ix]+[0,ddeps])-d[ix])/ddeps # gradient
					dgrad2 = dgradx**2 + dgrady**2
					p[ix] -= (d[ix]*np.vstack((dgradx, dgrady))/dgrad2).T # Project
		
			# 8. Termination criterion: All interior nodes move less than dptol (scaled)
			if (np.sqrt((self.deltat*Ftot[d<-self.geps]**2).sum(1))/self.h0).max() < self.dptol:
				break
		
		self.p = p 
		self.t = t 
		self.bars = bars
		self.L = L 
Example #6
0
def distmesh2d(fd,
               fh,
               h0,
               bbox,
               pfix=None,
               fig='gcf',
               dptol=.001,
               ttol=.1,
               Fscale=1.2,
               deltat=.2,
               geps_multiplier=.001,
               densityctrlfreq=30):
    """
    distmesh2d: 2-D Mesh Generator using Distance Functions.

    Usage
    -----
    >>> p, t = distmesh2d(fd, fh, h0, bbox, pfix)

    Parameters
    ----------
    fd:        Distance function d(x,y)
    fh:        Scaled edge length function h(x,y)
    h0:        Initial edge length
    bbox:      Bounding box, (xmin, ymin, xmax, ymax)
    pfix:      Fixed node positions, shape (nfix, 2)
    fig:       Figure to use for plotting, or None to disable plotting.

    Returns
    -------
    p:         Node positions (Nx2)
    t:         Triangle indices (NTx3)

    Example: (Uniform Mesh on Unit Circle)
    >>> fd = lambda p: sqrt((p**2).sum(1))-1.0
    >>> p, t = distmesh2d(fd, huniform, 2, (-1,-1,1,1))

    Example: (Rectangle with circular hole, refined at circle boundary)
    >>> fd = lambda p: ddiff(drectangle(p,-1,1,-1,1), dcircle(p,0,0,0.5))
    >>> fh = lambda p: 0.05+0.3*dcircle(p,0,0,0.5)
    >>> p, t = distmesh2d(fd, fh, 0.05, (-1,-1,1,1),
                          [(-1,-1), (-1,1), (1,-1), (1,1)])

    Example: (Polygon)
    >>> pv=[(-0.4, -0.5), (0.4, -0.2), (0.4, -0.7), (1.5, -0.4), (0.9, 0.1),
            (1.6, 0.8), (0.5, 0.5), (0.2, 1.0), (0.1, 0.4), (-0.7, 0.7),
            (-0.4, -0.5)]
    >>> fd = lambda p: dpoly(p, pv)
    >>> p, t = distmesh2d(fd, huniform, 0.1, (-1,-1, 2,1), pv)

    Example: (Ellipse)
    >>> fd = lambda p: p[:,0]**2/2**2 + p[:,1]**2/1**2 - 1
    >>> p, t = dm.distmesh2d(fd, dm.huniform, 0.2, (-2,-1, 2,1))

    Example: (Square, with size function point and line sources)
    >>> fd = lambda p: dm.drectangle(p,0,1,0,1)
    >>> fh = lambda p: np.minimum(np.minimum(
            0.01+0.3*abs(dm.dcircle(p,0,0,0)),
            0.025+0.3*abs(dm.dpoly(p,[(0.3,0.7),(0.7,0.5)]))), 0.15)
    >>> p, t = dm.distmesh2d(fd, fh, 0.01, (0,0,1,1), [(0,0),(1,0),(0,1),(1,1)])

    Example: (NACA0012 airfoil)
    >>> hlead=0.01; htrail=0.04; hmax=2; circx=2; circr=4
    >>> a=.12/.2*np.array([0.2969,-0.1260,-0.3516,0.2843,-0.1036])
    >>> a0=a[0]; a1=np.hstack((a[5:0:-1], 0.0))
    >>> fd = lambda p: dm.ddiff(
        dm.dcircle(p,circx,0,circr),
        (abs(p[:,1])-np.polyval(a1, p[:,0]))**2-a0**2*p[:,0])
    >>> fh = lambda p: np.minimum(np.minimum(
            hlead+0.3*dm.dcircle(p,0,0,0),
            htrail+0.3*dm.dcircle(p,1,0,0)), hmax)

    >>> fixx = 1.0-htrail*np.cumsum(1.3**np.arange(5))
    >>> fixy = a0*np.sqrt(fixx)+np.polyval(a1, fixx)
    >>> fix = np.vstack((
            np.array([(circx-circr,0),(circx+circr,0),
                      (circx,-circr),(circx,circr),
                      (0,0),(1,0)]),
            np.vstack((fixx, fixy)).T,
            np.vstack((fixx, -fixy)).T))
    >>> box = (circx-circr,-circr, circx+circr,circr)
    >>> h0 = min(hlead, htrail, hmax)
    >>> p, t = dm.distmesh2d(fd, fh, h0, box, fix)
    """

    if fig == 'gcf':
        import matplotlib.pyplot as plt
        fig = plt.gcf()

    geps = geps_multiplier * h0
    deps = np.sqrt(np.finfo(np.double).eps) * h0

    # Extract bounding box
    xmin, ymin, xmax, ymax = bbox
    if pfix is not None:
        pfix = np.array(pfix, dtype='d')

    # 0. Prepare a figure.
    if fig is not None:
        from distmesh.plotting import SimplexCollection
        fig.clf()
        ax = fig.gca()
        c = SimplexCollection()
        ax.add_collection(c)
        ax.set_xlim(xmin, xmax)
        ax.set_ylim(ymin, ymax)
        ax.set_aspect('equal')
        ax.set_axis_off()
        fig.canvas.draw()

    # 1. Create initial distribution in bounding box (equilateral triangles)
    x, y = np.mgrid[xmin:(xmax + h0):h0,
                    ymin:(ymax + h0 * np.sqrt(3) / 2):h0 * np.sqrt(3) / 2]
    x[:, 1::2] += h0 / 2  # Shift even rows
    p = np.vstack((x.flat, y.flat)).T  # List of node coordinates

    # 2. Remove points outside the region, apply the rejection method
    p = p[fd(p) < geps]  # Keep only d<0 points
    r0 = 1 / fh(p)**2  # Probability to keep point
    p = p[np.random.random(p.shape[0]) < r0 / r0.max()]  # Rejection method
    if pfix is not None:
        p = ml.setdiff_rows(p, pfix)  # Remove duplicated nodes
        pfix = ml.unique_rows(pfix)
        nfix = pfix.shape[0]
        p = np.vstack((pfix, p))  # Prepend fix points
    else:
        nfix = 0
    N = p.shape[0]  # Number of points N

    count = 0
    pold = float('inf')  # For first iteration

    while True:
        count += 1

        # 3. Retriangulation by the Delaunay algorithm
        dist = lambda p1, p2: np.sqrt(((p1 - p2)**2).sum(1))
        if (dist(p, pold) / h0).max() > ttol:  # Any large movement?
            pold = p.copy()  # Save current positions
            t = spspatial.Delaunay(p).vertices  # List of triangles
            pmid = p[t].sum(1) / 3  # Compute centroids
            t = t[fd(pmid) < -geps]  # Keep interior triangles
            # 4. Describe each bar by a unique pair of nodes
            bars = np.vstack((t[:, [0, 1]], t[:, [1, 2]],
                              t[:, [2, 0]]))  # Interior bars duplicated
            bars.sort(axis=1)
            bars = ml.unique_rows(bars)  # Bars as node pairs
            # 5. Graphical output of the current mesh
            if fig is not None:
                c.set_simplices((p, t))
                fig.canvas.draw()

        # 6. Move mesh points based on bar lengths L and forces F
        barvec = p[bars[:, 0]] - p[bars[:, 1]]  # List of bar vectors
        L = np.sqrt((barvec**2).sum(1))  # L = Bar lengths
        hbars = fh(p[bars].sum(1) / 2)
        L0 = (hbars * Fscale * np.sqrt(
            (L**2).sum() / (hbars**2).sum()))  # L0 = Desired lengths

        # Density control - remove points that are too close
        if (count % densityctrlfreq) == 0 and (L0 > 2 * L).any():
            ixdel = np.setdiff1d(bars[L0 > 2 * L].reshape(-1), np.arange(nfix))
            p = p[np.setdiff1d(np.arange(N), ixdel)]
            N = p.shape[0]
            pold = float('inf')
            continue

        F = L0 - L
        F[F < 0] = 0  # Bar forces (scalars)
        Fvec = F[:, None] / L[:, None].dot(
            [[1, 1]]) * barvec  # Bar forces (x,y components)
        Ftot = ml.dense(bars[:, [0, 0, 1, 1]],
                        np.repeat([[0, 1, 0, 1]], len(F), axis=0),
                        np.hstack((Fvec, -Fvec)),
                        shape=(N, 2))
        Ftot[:nfix] = 0  # Force = 0 at fixed points
        p += deltat * Ftot  # Update node positions

        # 7. Bring outside points back to the boundary
        d = fd(p)
        ix = d > 0  # Find points outside (d>0)
        if ix.any():
            dgradx = (fd(p[ix] + [deps, 0]) - d[ix]) / deps  # Numerical
            dgrady = (fd(p[ix] + [0, deps]) - d[ix]) / deps  # gradient
            dgrad2 = dgradx**2 + dgrady**2
            p[ix] -= (d[ix] * np.vstack(
                (dgradx, dgrady)) / dgrad2).T  # Project

        # 8. Termination criterion: All interior nodes move less than dptol (scaled)
        if (np.sqrt((deltat * Ftot[d < -geps]**2).sum(1)) / h0).max() < dptol:
            break

    # Clean up and plot final mesh
    p, t = dmutils.fixmesh(p, t)

    if fig is not None:
        c.set_simplices((p, t))
        fig.canvas.draw()

    return p, t