예제 #1
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
예제 #2
0
def distmeshnd(fd, fh, h0, bbox, pfix=None, fig='gcf'):
    """
    distmeshnd: N-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, zmin, ..., xmax, ymax, zmax, ...)
    pfix:      Fixed node positions, shape (nfix, dim)
    fig:       Figure to use for plotting, or None to disable plotting.

    Returns
    -------
    p:         Node positions (np, dim)
    t:         Triangle indices (nt, dim+1)

    Example: (Unit ball)
    >>> dim = 3
    >>> fd = lambda p: sqrt((p**2).sum(1))-1.0
    >>> bbox = np.vstack((-np.ones(dim), np.ones(dim)))
    >>> p, t = distmeshnd(fd, huniform, 2, bbox)
    """

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

    bbox = np.array(bbox).reshape(2, -1)
    dim = bbox.shape[1]

    ptol=.001; ttol=.1; L0mult=1+.4/2**(dim-1); deltat=.1; geps=1e-1*h0;
    deps=np.sqrt(np.finfo(np.double).eps)*h0;

    if pfix is not None:
        pfix = np.array(pfix, dtype='d')
        nfix = len(pfix)
    else:
        pfix = np.empty((0, dim))
        nfix = 0

    # 0. Prepare a figure.
    if fig is not None:
        if dim == 2:
            from distmesh.plotting import SimplexCollection
            fig.clf()
            ax = fig.gca()
            c = SimplexCollection()
            ax.add_collection(c)
            ax.set_xlim(bbox[:,0])
            ax.set_ylim(bbox[:,1])
            ax.set_aspect('equal')
            ax.set_axis_off()
            fig.canvas.draw()
        elif dim == 3:
            import mpl_toolkits.mplot3d
            from distmesh.plotting import axes_simpplot3d
            fig.clf()
            ax = fig.gca(projection='3d')
            ax.set_xlim(bbox[:,0])
            ax.set_ylim(bbox[:,1])
            ax.set_zlim(bbox[:,2])
            fig.canvas.draw()
        else:
            print("Plotting only supported in dimensions 2 and 3.")
            fig = None

    # 1. Create initial distribution in bounding box (equilateral triangles)
    p = np.mgrid[tuple(slice(min, max+h0, h0) for min, max in bbox.T)]
    p = p.reshape(dim, -1).T

    # 2. Remove points outside the region, apply the rejection method
    p = p[fd(p)<geps]                                # Keep only d<0 points
    r0 = fh(p)
    p = np.vstack((pfix,
                   p[np.random.rand(p.shape[0]) < r0.min()**dim / r0**dim]))
    N = p.shape[0]

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

    while True:

        # 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)/(dim+1)               # Compute centroids
            t = t[fd(pmid) < -geps]                  # Keep interior triangles
            # 4. Describe each bar by a unique pair of nodes
            bars = np.vstack((t[:,pair] for pair in
                              itertools.combinations(range(dim+1), 2)))
            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:
                if dim == 2:
                    c.set_simplices((p,t))
                    fig.canvas.draw()

                elif dim == 3:
                    if count % 5 == 0:
                        ax.cla()
                        axes_simpplot3d(ax, p, t, p[:,1] > 0)
                        ax.set_title('Retriangulation #%d' % count)
                        fig.canvas.draw()
            else:
                print('Retriangulation #%d' % count)
            count += 1

        # 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*L0mult*((L**dim).sum()/(hbars**dim).sum())**(1.0/dim))
        F = L0-L; F[F<0] = 0                         # Bar forces (scalars)
        Fvec = F[:,None]/L[:,None].dot(np.ones((1,dim)))*barvec # Bar forces (x,y components)
        Ftot = ml.dense(bars[:,[0]*dim+[1]*dim],
                        np.repeat([list(range(dim))*2], len(F), axis=0),
                        np.hstack((Fvec, -Fvec)),
                        shape=(N, dim))
        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():
            def deps_vec(i): a = [0]*dim; a[i] = deps; return a
            dgrads = [(fd(p[ix]+deps_vec(i))-d[ix])/deps for i in range(dim)]
            dgrad2 = sum(dgrad**2 for dgrad in dgrads)
            p[ix] -= (d[ix]*np.vstack(dgrads)/dgrad2).T # Project

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

    return p, t
예제 #3
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