def elastic3d_displacement(x, p, lamb=1.0, mu=1.0, **kwargs): ''' Returns a collection of weight matrices that estimates displacements at `x` based on displacements at `p`. If `x` is in `p` then the results will be an appropriately shaped identity matrix. Parameters ---------- x : (N,3) array target points. p : (M,3) array observation points. lamb : float first Lame parameter mu : float second Lame parameter **kwargs : additional arguments passed to `weight_matrix` Returns ------- out : (3,3) list of sparse matrices A collection of matrices which return the displacements at `p` based on the displacements at `x`. ''' D_xx = weight_matrix(x, p, (0, 0, 0), **kwargs) D_yy = weight_matrix(x, p, (0, 0, 0), **kwargs) D_zz = weight_matrix(x, p, (0, 0, 0), **kwargs) return {'xx':D_xx, 'yy':D_yy, 'zz':D_zz}
def elastic3d_body_force(x, p, lamb=1.0, mu=1.0, **kwargs): ''' Returns a collection of weight matrices used to calculate body force in a three-dimensional homogeneous elastic medium. Parameters ---------- x : (N, 3) array target points. p : (M, 3) array observation points. lamb : float first Lame parameter mu : float second Lame parameter **kwargs : additional arguments passed to `weight_matrix` Returns ------- out : (3, 3) list of sparse matrices A collection of matrices which return the body force at `x` exerted by the material, when dotted with the displacements at `p`. ''' coeffs_xx = [lamb + 2*mu, mu, mu] diffs_xx = [ (2, 0, 0), (0, 2, 0), (0, 0, 2)] coeffs_xy = [lamb + mu] diffs_xy = [(1, 1, 0)] coeffs_xz = [lamb + mu] diffs_xz = [(1, 0, 1)] coeffs_yx = [lamb + mu] diffs_yx = [(1, 1, 0)] coeffs_yy = [ mu, lamb + 2*mu, mu] diffs_yy = [(2, 0, 0), (0, 2, 0), (0, 0, 2)] coeffs_yz = [lamb + mu] diffs_yz = [(0, 1, 1)] coeffs_zx = [lamb + mu] diffs_zx = [(1, 0, 1)] coeffs_zy = [lamb + mu] diffs_zy = [(0, 1, 1)] coeffs_zz = [ mu, mu, lamb + 2*mu] diffs_zz = [(2, 0, 0), (0, 2, 0), (0, 0, 2)] D_xx = weight_matrix(x, p, diffs_xx, coeffs=coeffs_xx, **kwargs) D_xy = weight_matrix(x, p, diffs_xy, coeffs=coeffs_xy, **kwargs) D_xz = weight_matrix(x, p, diffs_xz, coeffs=coeffs_xz, **kwargs) D_yx = weight_matrix(x, p, diffs_yx, coeffs=coeffs_yx, **kwargs) D_yy = weight_matrix(x, p, diffs_yy, coeffs=coeffs_yy, **kwargs) D_yz = weight_matrix(x, p, diffs_yz, coeffs=coeffs_yz, **kwargs) D_zx = weight_matrix(x, p, diffs_zx, coeffs=coeffs_zx, **kwargs) D_zy = weight_matrix(x, p, diffs_zy, coeffs=coeffs_zy, **kwargs) D_zz = weight_matrix(x, p, diffs_zz, coeffs=coeffs_zz, **kwargs) return {'xx':D_xx, 'xy':D_xy, 'xz':D_xz, 'yx':D_yx, 'yy':D_yy, 'yz':D_yz, 'zx':D_zx, 'zy':D_zy, 'zz':D_zz}
def elastic2d_surface_force(x, nrm, p, lamb=1.0, mu=1.0, **kwargs): ''' Returns a collection of weight matrices that estimate surface traction forces at *x* resulting from displacements at *p*. Parameters ---------- x : (N,2) array target points which reside on a surface. nrm : (N,2) array surface normal vectors at each point in *x*. p : (M,2) array observation points. lamb : float first Lame parameter mu : float second Lame parameter **kwargs : additional arguments passed to *weight_matrix* Returns ------- out : (2,2) list of sparse matrices A collection of matrices [[D_xx,D_xy],[D_yx,D_yy]] which return the surface traction force [t_x,t_y] at *x* exerted by the material, when dotted with the displacements [u_x,u_y] at *p*. ''' # x component of traction force resulting from x displacement coeffs_xx = [nrm[:, 0] * (lamb + 2 * mu), nrm[:, 1] * mu] diffs_xx = [(1, 0), (0, 1)] # x component of traction force resulting from y displacement coeffs_xy = [nrm[:, 0] * lamb, nrm[:, 1] * mu] diffs_xy = [(0, 1), (1, 0)] # y component of traction force resulting from x displacement coeffs_yx = [nrm[:, 0] * mu, nrm[:, 1] * lamb] diffs_yx = [(0, 1), (1, 0)] # y component of force resulting from displacement in the y direction coeffs_yy = [nrm[:, 0] * mu, nrm[:, 1] * (lamb + 2 * mu)] diffs_yy = [(1, 0), (0, 1)] # make the differentiation matrices that enforce the free surface boundary # conditions. D_xx = weight_matrix(x, p, diffs_xx, coeffs=coeffs_xx, **kwargs) D_xy = weight_matrix(x, p, diffs_xy, coeffs=coeffs_xy, **kwargs) D_yx = weight_matrix(x, p, diffs_yx, coeffs=coeffs_yx, **kwargs) D_yy = weight_matrix(x, p, diffs_yy, coeffs=coeffs_yy, **kwargs) return [[D_xx, D_xy], [D_yx, D_yy]]
def elastic2d_body_force(x, p, lamb=1.0, mu=1.0, **kwargs): ''' Returns a collection of weight matrices used to calculate body force in a two-dimensional homogeneous elastc medium. Parameters ---------- x : (N,2) array Target points. p : (M,2) array Observation points. lamb : float, optional First Lame parameter mu : float, optional Specond Lame parameter **kwargs : additional arguments passed to *weight_matrix* Returns ------- out : (2,2) list of sparse matrices A collection of matrices [[D_xx,D_xy],[D_yx,D_yy]] which return the body force [f_x,f_y] at *x* exerted by the material, when dotted with the displacements [u_x,u_y] at *p*. ''' # x component of force resulting from displacement in the x direction. coeffs_xx = [lamb + 2 * mu, mu] diffs_xx = [(2, 0), (0, 2)] # x component of force resulting from displacement in the y direction. coeffs_xy = [lamb + mu] diffs_xy = [(1, 1)] # y component of force resulting from displacement in the x direction. coeffs_yx = [lamb + mu] diffs_yx = [(1, 1)] # y component of force resulting from displacement in the y direction. coeffs_yy = [lamb + 2 * mu, mu] diffs_yy = [(0, 2), (2, 0)] # make the differentiation matrices that enforce the PDE on the # interior nodes. D_xx = weight_matrix(x, p, diffs_xx, coeffs=coeffs_xx, **kwargs) D_xy = weight_matrix(x, p, diffs_xy, coeffs=coeffs_xy, **kwargs) D_yx = weight_matrix(x, p, diffs_yx, coeffs=coeffs_yx, **kwargs) D_yy = weight_matrix(x, p, diffs_yy, coeffs=coeffs_yy, **kwargs) return [[D_xx, D_xy], [D_yx, D_yy]]
def elastic3d_displacement(x, p, lamb=1.0, mu=1.0, **kwargs): ''' Returns a collection of weight matrices that estimates displacements at *x* based on displacements at *p*. If *x* is in *p* then the results will be an appropriately shaped identity matrix. Parameters ---------- x : (N,3) array target points. p : (M,3) array observation points. lamb : float first Lame parameter mu : float second Lame parameter **kwargs : additional arguments passed to *weight_matrix* Returns ------- out : (3,3) list of sparse matrices A collection of matrices which return the displacements at *p* based on the displacements at *x*. ''' D_xx = weight_matrix(x, p, (0, 0, 0), **kwargs) D_xy = csr_matrix((x.shape[0], p.shape[0])) D_xz = csr_matrix((x.shape[0], p.shape[0])) D_yx = csr_matrix((x.shape[0], p.shape[0])) D_yy = weight_matrix(x, p, (0, 0, 0), **kwargs) D_yz = csr_matrix((x.shape[0], p.shape[0])) D_zx = csr_matrix((x.shape[0], p.shape[0])) D_zy = csr_matrix((x.shape[0], p.shape[0])) D_zz = weight_matrix(x, p, (0, 0, 0), **kwargs) return [[D_xx, D_xy, D_xz], [D_yx, D_yy, D_yz], [D_zx, D_zy, D_zz]]
# identify nodes associated with the different boundary types slit_top, = (smpid == 199).nonzero() slit_bot, = (smpid == 99).nonzero() # edges of the annulus minus the cut boundary, = ((smpid >= 0) & (smpid != 199) & (smpid != 99)).nonzero() interior, = (smpid == -1).nonzero() # do not build stencils which cross this line bnd_vert = np.array([[0.0, 0.0], [10 * np.cos(gap / 2.0), 10 * np.sin(gap / 2.0)]]) bnd_smp = np.array([[0, 1]]) weight_kwargs = {'vert': bnd_vert, 'smp': bnd_smp, 'n': 20} # build lhs # enforce laplacian on interior nodes A_interior = weight_matrix(nodes[interior], nodes, [[2, 0], [0, 2]], **weight_kwargs) # find boundary normal vectors normals = simplex_outward_normals(vert, smp)[smpid[boundary]] n1 = scipy.sparse.diags(normals[:, 0], 0) n2 = scipy.sparse.diags(normals[:, 1], 0) # enforce free surface boundary conditions A_boundary = ( n1 * weight_matrix(nodes[boundary], nodes, [1, 0], **weight_kwargs) + n2 * weight_matrix(nodes[boundary], nodes, [0, 1], **weight_kwargs)) # These next two matrices are really just identity matrices padded with zeros A_slit_top = weight_matrix(nodes[slit_top], nodes, [0, 0], **weight_kwargs) A_slit_bot = weight_matrix(nodes[slit_bot], nodes, [0, 0], **weight_kwargs) # stack all the matrices
d_z[groups['boundary:sides']] = 0.0 d_z[groups['boundary:bottom']] = 0.0 d_z[groups['pinned']] = 0.0 d = np.hstack((d_x, d_y, d_z)) # solve the system using an ILU decomposition and GMRES u = gmres_ilu_solve(G, d) u = np.reshape(u,(3, -1)) u_x, u_y, u_z = u # Calculate strain and stress from displacements D_x = weight_matrix( nodes, nodes, (1, 0, 0), n=stencil_size, basis=basis, order=poly_order) D_y = weight_matrix( nodes, nodes, (0, 1, 0), n=stencil_size, basis=basis, order=poly_order) D_z = weight_matrix( nodes, nodes,
# build the "right hand side" vectors for roller constraints b_roller_n = np.zeros_like(roller_idx) # enforce zero normal displacement b_roller_p1 = np.zeros_like(roller_idx) # enforce zero parallel traction b_roller_p2 = np.zeros_like(roller_idx) # enforce zero parallel traction # stack it all together and solve A = vstack((A_body_x,A_body_y,A_body_z, A_surf_x,A_surf_y,A_surf_z, A_roller_n,A_roller_p1,A_roller_p2)).tocsr() b = np.hstack((b_body_x,b_body_y,b_body_z, b_surf_x,b_surf_y,b_surf_z, b_roller_n,b_roller_p1,b_roller_p2)) u = spsolve(A,b,permc_spec='MMD_ATA') u = np.reshape(u,(3,-1)) u_x,u_y,u_z = u # Calculate strain from displacements D_x = weight_matrix(nodes,nodes,(1,0,0),n=n) D_y = weight_matrix(nodes,nodes,(0,1,0),n=n) D_z = weight_matrix(nodes,nodes,(0,0,1),n=n) e_xx = D_x.dot(u_x) e_yy = D_y.dot(u_y) e_zz = D_z.dot(u_z) e_xy = 0.5*(D_y.dot(u_x) + D_x.dot(u_y)) e_xz = 0.5*(D_z.dot(u_x) + D_x.dot(u_z)) e_yz = 0.5*(D_z.dot(u_y) + D_y.dot(u_z)) # Calculate stress from Hooks law s_xx = (2*mu + lamb)*e_xx + lamb*e_yy + lamb*e_zz s_yy = lamb*e_xx + (2*mu + lamb)*e_yy + lamb*e_zz s_zz = lamb*e_xx + lamb*e_yy + (2*mu + lamb)*e_zz s_xy = 2*mu*e_xy s_xz = 2*mu*e_xz s_yz = 2*mu*e_yz
mu=mu, n=stencil_size, order=order, basis=basis) D_xx = add_rows(D_xx, components['xx'], idx['boundary:all']) D_xy = add_rows(D_xy, components['xy'], idx['boundary:all']) D_yx = add_rows(D_xy, components['yx'], idx['boundary:all']) D_yy = add_rows(D_yy, components['yy'], idx['boundary:all']) # the ghost node components are left as zero D = sp.vstack((sp.hstack((D_xx, D_xy)), sp.hstack((D_yx, D_yy)))).tocsc() L = weight_matrix(nodes, nodes, diffs=[[6, 0], [0, 6]], n=stencil_size, basis=rbf.basis.phs7, order=6) L = sp.block_diag((L, L)) I = sp.eye(L.shape[0]) R = np.linalg.inv((I + 1e-14 * L.T.dot(L)).A) def f(t, z): ''' Function used for time integration. This calculates the time derivative of the current state vector. ''' u, v = z.reshape((2, -1)) dudt = v h = Binv.dot(u)
# add ghost nodes ghost_nodes = make_ghost_nodes(nodes,smpid,boundary,vert,smp) nodes = np.vstack((nodes,ghost_nodes)) # ghost node indices ghost = N + np.arange(len(boundary)) # do not build stencils which cross this line bnd_vert = np.array([[0.0,0.0],[5.0,0.0]]) bnd_smp = np.array([[0,1]]) weight_kwargs = {'vert':bnd_vert,'smp':bnd_smp,'n':20,'order':2} # build lhs # enforce laplacian on interior nodes A_interior = weight_matrix(nodes[interior],nodes, [[2,0],[0,2]], **weight_kwargs) A_ghost = weight_matrix(nodes[boundary],nodes, [[2,0],[0,2]], **weight_kwargs) # find boundary normal vectors normals = simplex_outward_normals(vert,smp)[smpid[boundary]] n1 = scipy.sparse.diags(normals[:,0],0) n2 = scipy.sparse.diags(normals[:,1],0) # enforce free surface boundary conditions A_boundary = (n1*weight_matrix(nodes[boundary],nodes,[1,0],**weight_kwargs) + n2*weight_matrix(nodes[boundary],nodes,[0,1],**weight_kwargs)) # These next two matrices are really just identity matrices padded with zeros A_slit_top = weight_matrix(nodes[slit_top],nodes,[0,0],**weight_kwargs)
# generate nodes nodes, groups, normals = min_energy_nodes(N_nominal, vert, smp, boundary_groups=boundary_groups, boundary_groups_with_ghosts=['free'], include_vertices=True) N = nodes.shape[0] # create the "left hand side" matrix. # create the component which evaluates the PDE A_interior = weight_matrix(nodes[groups['interior']], nodes, n=n, diffs=[[2, 0], [0, 2]], basis=basis, order=order) # use the ghost nodes to evaluate the PDE at the free boundary nodes A_ghost = weight_matrix(nodes[groups['boundary:free']], nodes, n=n, diffs=[[2, 0], [0, 2]], basis=basis, order=order) # create the component for the fixed boundary conditions. This is # essentially an identity operation and so we only need a stencil size # of 1 A_fixed = weight_matrix(nodes[groups['boundary:fixed']],
from rbf.geometry import contains import matplotlib.pyplot as plt from scipy.integrate import ode from scipy.interpolate import griddata # define the problem domain vert = np.array([[0.0, 0.0], [2.0, 0.0], [2.0, 1.0], [1.0, 1.0], [1.0, 2.0], [0.0, 2.0]]) smp = np.array([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0]]) times = np.linspace(0.0, 2.0, 5) # output times N = 50000 # total number of nodes nodes, smpid = menodes(N, vert, smp) # generate nodes interior = np.nonzero(smpid == -1)[0].tolist() # identify boundary nodes boundary = np.nonzero(smpid >= 0)[0].tolist() # identify boundary nodes # create differentiation matrices for the interior and boundary nodes D = weight_matrix(nodes[interior], nodes, [(2, 0), (0, 2)], n=30) # create initial and boundary conditions r = np.sqrt((nodes[interior, 0] - 0.5)**2 + (nodes[interior, 1] - 0.5)**2) u_init = 1.0 / (1 + (r / 0.05)**4) # initial u in the interior dudt_init = np.zeros(len(interior)) # initial velocity in the interior u_bnd = np.zeros(len(boundary)) # boundary conditions # Make state vector containing the initial displacements and velocities v = np.hstack([u_init, dudt_init]) def f(t, v): ''' Function used for time integration. This calculates the time derivative of the current state vector. ''' v = v.reshape((2, -1))
# define the problem domain vert = np.array([[0.762,0.057],[0.492,0.247],[0.225,0.06 ],[0.206,0.056], [0.204,0.075],[0.292,0.398],[0.043,0.609],[0.036,0.624], [0.052,0.629],[0.373,0.63 ],[0.479,0.953],[0.49 ,0.966], [0.503,0.952],[0.611,0.629],[0.934,0.628],[0.95 ,0.622], [0.941,0.607],[0.692,0.397],[0.781,0.072],[0.779,0.055]]) smp = np.array([[0,1],[1,2],[2,3],[3,4],[4,5],[5,6],[6,7],[7,8],[8,9], [9,10],[10,11],[11,12],[12,13],[13,14],[14,15],[15,16], [16,17],[17,18],[18,19],[19,0]]) dt = 0.000025 # time step size N = 100000 # total number of nodes nodes,smpid = menodes(N,vert,smp) # generate nodes boundary, = (smpid>=0).nonzero() # identify boundary nodes interior, = (smpid==-1).nonzero() # identify interior nodes D = weight_matrix(nodes[interior],nodes,[[2,0],[0,2]],n=30) r = np.linalg.norm(nodes-np.array([0.49,0.46]),axis=1) u_prev = 1.0/(1 + (r/0.01)**4) # create initial conditions u_curr = 1.0/(1 + (r/0.01)**4) fig,axs = plt.subplots(2,2,figsize=(7,7)) axs = [axs[0][0],axs[0][1],axs[1][0],axs[1][1]] for i in range(15001): u_next = dt**2*D.dot(u_curr) + 2*u_curr[interior] - u_prev[interior] u_prev[:] = u_curr u_curr[interior] = u_next if i in [0,5000,10000,15000]: ax = axs[[0,5000,10000,15000].index(i)] p = ax.scatter(nodes[:,0],nodes[:,1],s=3,c=np.array(u_curr,copy=True), edgecolor='none',cmap='viridis',vmin=-0.1,vmax=0.1) for s in smp: ax.plot(vert[s,0],vert[s,1],'k-') ax.set_aspect('equal')
# performed well for me and they do not require the user # to tune a shape parameter. Use higher order # polyharmonic splines for higher order PDEs. order = 2 # Order of the added polynomials. This should be at least as # large as the order of the PDE being solved (2 in this # case). Larger values may improve accuracy # generate nodes nodes, smpid = menodes(N, vert, smp) edge_idx, = (smpid >= 0).nonzero() interior_idx, = (smpid == -1).nonzero() # create "left hand side" matrix A_int = weight_matrix(nodes[interior_idx], nodes, diffs=[[2, 0], [0, 2]], n=n, basis=basis, order=order) A_edg = weight_matrix(nodes[edge_idx], nodes, diffs=[0, 0]) A = vstack((A_int, A_edg)) # create "right hand side" vector d_int = -1 * np.ones_like(interior_idx) d_edg = np.zeros_like(edge_idx) d = np.hstack((d_int, d_edg)) # find the solution at the nodes u_soln = spsolve(A, d) # interpolate the solution on a grid xg, yg = np.meshgrid(np.linspace(-0.05, 2.05, 400), np.linspace(-0.05, 2.05, 400)) points = np.array([xg.flatten(), yg.flatten()]).T u_itp = LinearNDInterpolator(nodes, u_soln)(points)
# order polyharmonic splines (e.g., phs3) have always # performed well for me and they do not require the user # to tune a shape parameter. Use higher order # polyharmonic splines for higher order PDEs. order = 2 # Order of the added polynomials. This should be at least as # large as the order of the PDE being solved (2 in this # case). Larger values may improve accuracy # generate nodes nodes, groups, _ = min_energy_nodes(N, vert, smp) # create the "left hand side" matrix. # create the component which evaluates the PDE A_interior = weight_matrix(nodes[groups['interior']], nodes, diffs=[[2, 0], [0, 2]], n=n, basis=basis, order=order) # create the component for the fixed boundary conditions A_boundary = weight_matrix(nodes[groups['boundary:all']], nodes, diffs=[0, 0]) # Add the components to the corresponding rows of `A` A = csc_matrix((N, N)) A = add_rows(A,A_interior,groups['interior']) A = add_rows(A,A_boundary,groups['boundary:all']) # create "right hand side" vector d = np.zeros((N,)) d[groups['interior']] = -1.0 d[groups['boundary:all']] = 0.0 # find the solution at the nodes
# define a circular domain vert,smp = rbf.domain.circle() nodes,smpid = menodes(N,vert,smp) # smpid describes which boundary simplex, if any, the nodes are # attached to. If it is -1, then the node is in the interior boundary, = (smpid>=0).nonzero() interior, = (smpid==-1).nonzero() # create the left-hand-side matrix which is the Laplacian of the basis # function for interior nodes and the undifferentiated basis functions # for the boundary nodes. The third argument to weight_matrix # describes the derivates order for each spatial dimension A = scipy.sparse.lil_matrix((N,N)) A[interior,:] = weight_matrix(nodes[interior],nodes,[[2,0],[0,2]]) A[boundary,:] = weight_matrix(nodes[boundary],nodes,[0,0]) # convert A to a csr matrix for efficient solving A = A.tocsr() # create the right-hand-side vector, consisting of the forcing term # for the interior nodes and zeros for the boundary nodes d = np.zeros(N) d[interior] = forcing(nodes[interior,0],nodes[interior,1]) d[boundary] = true_soln(nodes[boundary,0],nodes[boundary,1]) # find the solution at the nodes u = scipy.sparse.linalg.spsolve(A,d) err = u - true_soln(nodes[:,0],nodes[:,1]) # plot the results
# define the problem domain vert = np.array([[0.762, 0.057], [0.492, 0.247], [0.225, 0.06], [0.206, 0.056], [0.204, 0.075], [0.292, 0.398], [0.043, 0.609], [0.036, 0.624], [0.052, 0.629], [0.373, 0.63], [0.479, 0.953], [0.49, 0.966], [0.503, 0.952], [0.611, 0.629], [0.934, 0.628], [0.95, 0.622], [0.941, 0.607], [0.692, 0.397], [0.781, 0.072], [0.779, 0.055]]) smp = np.array([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 6], [6, 7], [7, 8], [8, 9], [9, 10], [10, 11], [11, 12], [12, 13], [13, 14], [14, 15], [15, 16], [16, 17], [17, 18], [18, 19], [19, 0]]) dt = 0.000025 # time step size N = 100000 # total number of nodes nodes, smpid = menodes(N, vert, smp) # generate nodes boundary, = (smpid >= 0).nonzero() # identify boundary nodes interior, = (smpid == -1).nonzero() # identify interior nodes D = weight_matrix(nodes[interior], nodes, [[2, 0], [0, 2]], n=30) r = np.linalg.norm(nodes - np.array([0.49, 0.46]), axis=1) u_prev = 1.0 / (1 + (r / 0.01)**4) # create initial conditions u_curr = 1.0 / (1 + (r / 0.01)**4) fig, axs = plt.subplots(2, 2, figsize=(7, 7)) axs = [axs[0][0], axs[0][1], axs[1][0], axs[1][1]] for i in range(15001): u_next = dt**2 * D.dot(u_curr) + 2 * u_curr[interior] - u_prev[interior] u_prev[:] = u_curr u_curr[interior] = u_next if i in [0, 5000, 10000, 15000]: ax = axs[[0, 5000, 10000, 15000].index(i)] p = ax.scatter(nodes[:, 0], nodes[:, 1], s=3, c=np.array(u_curr, copy=True),
from rbf.fd import weight_matrix from rbf.nodes import min_energy_nodes from rbf.geometry import contains import matplotlib.pyplot as plt from scipy.integrate import ode from scipy.interpolate import griddata # define the problem domain vert = np.array([[0.0, 0.0], [2.0, 0.0], [2.0, 1.0], [1.0, 1.0], [1.0, 2.0], [0.0, 2.0]]) smp = np.array([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0]]) times = np.linspace(0.0, 2.0, 5) # output times N = 20000 # total number of nodes nodes, idx, _ = min_energy_nodes(N, vert, smp) # generate nodes # create differentiation matrices for the interior and boundary nodes D = weight_matrix(nodes[idx['interior']], nodes, [(2, 0), (0, 2)], n=30) # create initial and boundary conditions r = np.sqrt((nodes[idx['interior'], 0] - 0.5)**2 + (nodes[idx['interior'], 1] - 0.5)**2) u_init = 1.0 / (1 + (r / 0.05)**4) # initial u in the interior dudt_init = np.zeros(len(idx['interior'])) # initial velocity in the interior u_bnd = np.zeros(len(idx['boundary:all'])) # boundary conditions # Make state vector containing the initial displacements and velocities v = np.hstack([u_init, dudt_init]) def f(t, v): ''' Function used for time integration. This calculates the time derivative of the current state vector. '''
## Enforce the PDE on interior nodes AND the free surface nodes # x component of force resulting from displacement in the x direction. coeffs_xx = [lamb+2*mu, mu] diffs_xx = [(2, 0), (0, 2)] # x component of force resulting from displacement in the y direction. coeffs_xy = [lamb, mu] diffs_xy = [(1, 1), (1, 1)] # y component of force resulting from displacement in the x direction. coeffs_yx = [mu, lamb] diffs_yx = [(1, 1), (1, 1)] # y component of force resulting from displacement in the y direction. coeffs_yy = [lamb+2*mu, mu] diffs_yy = [(0, 2), (2, 0)] # make the differentiation matrices that enforce the PDE on the # interior nodes. D_xx = weight_matrix(nodes[groups['interior']], nodes, diffs_xx, coeffs=coeffs_xx, n=n) D_xy = weight_matrix(nodes[groups['interior']], nodes, diffs_xy, coeffs=coeffs_xy, n=n) D_yx = weight_matrix(nodes[groups['interior']], nodes, diffs_yx, coeffs=coeffs_yx, n=n) D_yy = weight_matrix(nodes[groups['interior']], nodes, diffs_yy, coeffs=coeffs_yy, n=n) G_xx = add_rows(G_xx, D_xx, groups['interior']) G_xy = add_rows(G_xy, D_xy, groups['interior']) G_yx = add_rows(G_yx, D_yx, groups['interior']) G_yy = add_rows(G_yy, D_yy, groups['interior']) # use the ghost nodes to enforce the PDE on the boundary D_xx = weight_matrix(nodes[groups['boundary:free']], nodes, diffs_xx, coeffs=coeffs_xx, n=n) D_xy = weight_matrix(nodes[groups['boundary:free']], nodes, diffs_xy, coeffs=coeffs_xy, n=n) D_yx = weight_matrix(nodes[groups['boundary:free']], nodes, diffs_yx, coeffs=coeffs_yx, n=n) D_yy = weight_matrix(nodes[groups['boundary:free']], nodes, diffs_yy, coeffs=coeffs_yy, n=n) G_xx = add_rows(G_xx, D_xx, groups['ghosts:free']) G_xy = add_rows(G_xy, D_xy, groups['ghosts:free'])
[0.0, 2.0]]) smp = np.array([[0, 1], [1, 2], [2, 3], [3, 4], [4, 5], [5, 0]]) times = np.linspace(0.0, 2.0, 5) # output times N = 50000 # total number of nodes nodes, smpid = menodes(N, vert, smp) # generate nodes interior = np.nonzero(smpid == -1)[0].tolist() # identify boundary nodes boundary = np.nonzero(smpid >= 0)[0].tolist() # identify boundary nodes # calculate surface normal vector for each boundary node normals = simplex_outward_normals(vert, smp)[smpid[boundary]] # dx is the shortest distance between any two nodes dx = np.min(neighbors(nodes, 2)[1][:, 1]) # add ghost nodes to greatly improve accuracy at the free surface nodes = np.vstack((nodes, nodes[boundary] + 0.5 * dx * normals)) ghost = range(N, N + len(boundary)) # ghost node indices # create differentiation matrices for the interior and boundary nodes D = weight_matrix(nodes[interior + boundary], nodes, [(2, 0), (0, 2)], n=30) dD = weight_matrix(nodes[boundary], nodes, [(1, 0), (0, 1)], coeffs=normals.T, n=30) # create initial and boundary conditions r = np.sqrt((nodes[interior + boundary, 0] - 0.5)**2 + (nodes[interior + boundary, 1] - 0.5)**2) u_init = 1.0 / (1 + (r / 0.05)**4) # initial u in the interior dudt_init = np.zeros(N) # initial velocity in the interior u_bnd = np.zeros(len(boundary)) # boundary conditions # Make state vector containing the initial displacements and velocities v = np.hstack([u_init, dudt_init]) def f(t, v):
def elastic3d_surface_force(x, nrm, p, lamb=1.0, mu=1.0, **kwargs): ''' Returns a collection of weight matrices that estimate surface traction forces at *x* resulting from displacements at *p*. Parameters ---------- x : (N,3) array target points which reside on a surface. nrm : (N,3) array surface normal vectors at each point in *x*. p : (M,3) array observation points. lamb : float first Lame parameter mu : float second Lame parameter **kwargs : additional arguments passed to *weight_matrix* Returns ------- out : (3,3) list of sparse matrices A collection of matrices which return the surface traction force at *x* exerted by the material, when dotted with the displacements at *p*. ''' coeffs_xx = [nrm[:, 0] * (lamb + 2 * mu), nrm[:, 1] * mu, nrm[:, 2] * mu] diffs_xx = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] coeffs_xy = [nrm[:, 0] * lamb, nrm[:, 1] * mu] diffs_xy = [(0, 1, 0), (1, 0, 0)] coeffs_xz = [nrm[:, 0] * lamb, nrm[:, 2] * mu] diffs_xz = [(0, 0, 1), (1, 0, 0)] coeffs_yx = [nrm[:, 0] * mu, nrm[:, 1] * lamb] diffs_yx = [(0, 1, 0), (1, 0, 0)] coeffs_yy = [nrm[:, 0] * mu, nrm[:, 1] * (lamb + 2 * mu), nrm[:, 2] * mu] diffs_yy = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] coeffs_yz = [nrm[:, 1] * lamb, nrm[:, 2] * mu] diffs_yz = [(0, 0, 1), (0, 1, 0)] coeffs_zx = [nrm[:, 0] * mu, nrm[:, 2] * lamb] diffs_zx = [(0, 0, 1), (1, 0, 0)] coeffs_zy = [nrm[:, 1] * mu, nrm[:, 2] * lamb] diffs_zy = [(0, 0, 1), (0, 1, 0)] coeffs_zz = [nrm[:, 0] * mu, nrm[:, 1] * mu, nrm[:, 2] * (lamb + 2 * mu)] diffs_zz = [(1, 0, 0), (0, 1, 0), (0, 0, 1)] D_xx = weight_matrix(x, p, diffs_xx, coeffs=coeffs_xx, **kwargs) D_xy = weight_matrix(x, p, diffs_xy, coeffs=coeffs_xy, **kwargs) D_xz = weight_matrix(x, p, diffs_xz, coeffs=coeffs_xz, **kwargs) D_yx = weight_matrix(x, p, diffs_yx, coeffs=coeffs_yx, **kwargs) D_yy = weight_matrix(x, p, diffs_yy, coeffs=coeffs_yy, **kwargs) D_yz = weight_matrix(x, p, diffs_yz, coeffs=coeffs_yz, **kwargs) D_zx = weight_matrix(x, p, diffs_zx, coeffs=coeffs_zx, **kwargs) D_zy = weight_matrix(x, p, diffs_zy, coeffs=coeffs_zy, **kwargs) D_zz = weight_matrix(x, p, diffs_zz, coeffs=coeffs_zz, **kwargs) return [[D_xx, D_xy, D_xz], [D_yx, D_yy, D_yz], [D_zx, D_zy, D_zz]]
# define a circular domain vert, smp = rbf.domain.circle() nodes, smpid = menodes(N, vert, smp) # smpid describes which boundary simplex, if any, the nodes are # attached to. If it is -1, then the node is in the interior boundary, = (smpid >= 0).nonzero() interior, = (smpid == -1).nonzero() # create the left-hand-side matrix which is the Laplacian of the basis # function for interior nodes and the undifferentiated basis functions # for the boundary nodes. The third argument to weight_matrix # describes the derivates order for each spatial dimension A = scipy.sparse.lil_matrix((N, N)) A[interior, :] = weight_matrix(nodes[interior], nodes, [[2, 0], [0, 2]]) A[boundary, :] = weight_matrix(nodes[boundary], nodes, [0, 0]) # convert A to a csr matrix for efficient solving A = A.tocsr() # create the right-hand-side vector, consisting of the forcing term # for the interior nodes and zeros for the boundary nodes d = np.zeros(N) d[interior] = forcing(nodes[interior, 0], nodes[interior, 1]) d[boundary] = true_soln(nodes[boundary, 0], nodes[boundary, 1]) # find the solution at the nodes u = scipy.sparse.linalg.spsolve(A, d) err = u - true_soln(nodes[:, 0], nodes[:, 1]) # plot the results
coeffs_xx = [lamb + 2 * mu, mu] diffs_xx = [(2, 0), (0, 2)] # x component of force resulting from displacement in the y direction. coeffs_xy = [lamb, mu] diffs_xy = [(1, 1), (1, 1)] # y component of force resulting from displacement in the x direction. coeffs_yx = [mu, lamb] diffs_yx = [(1, 1), (1, 1)] # y component of force resulting from displacement in the y direction. coeffs_yy = [lamb + 2 * mu, mu] diffs_yy = [(0, 2), (2, 0)] # make the differentiation matrices that enforce the PDE on the # interior nodes. D_xx = weight_matrix(nodes[int_idx + free_idx], nodes, diffs_xx, coeffs=coeffs_xx, n=n) D_xy = weight_matrix(nodes[int_idx + free_idx], nodes, diffs_xy, coeffs=coeffs_xy, n=n) D_yx = weight_matrix(nodes[int_idx + free_idx], nodes, diffs_yx, coeffs=coeffs_yx, n=n) D_yy = weight_matrix(nodes[int_idx + free_idx], nodes, diffs_yy,