def create_f_vector(x, y, simplices, c=2): triangles = mesh.all_triangles(simplices, x, y) f = np.zeros(x.shape) areas = calc_areas(triangles) for n, simp in enumerate(simplices): f[simp] += -c*areas[n]/3 return f
def assemble_global_matrix(x, y, simplices, func_elem, d=2, elem_dict=dict()): """ Assembles the global matrix K for a FEM system. Arguments: - x, y: (n,) array of vertex positions - simplices: (n, 3) array of vertex numbers that make up each element - func_elem: function that calculates the element matrix for a given element, must have signature: func_elem(tri, area, **kwargs) - d: dimensionality of the output space (ie, scalar field or vector field) - elem_dict: keyword arguments passed to func_elem Returns: - K: (nd, nd) Global matrix for the FEM system """ N = x.size K = np.zeros((N * d, N * d)) triangles = mesh.all_triangles(simplices, x, y) areas = calc_areas(triangles) indices = element_to_global(simplices, d) for tri, area, ind in zip(triangles, areas, indices): el = func_elem(tri, area, **elem_dict) if not np.allclose(el, el.T): print('Element matrix not symmetric') print(el) x, y = ind K[x, y] += el return K
def assemble_global_matrix(x, y, simplices): K = np.zeros((x.size, x.size)) triangles = mesh.all_triangles(simplices, x, y) elements = create_element_matrix(triangles) indices = get_global_indices(simplices) for el, ind in zip(elements, indices): x, y = ind K[x, y] += el return K
def ex_with_external(): x, y, simplices = load_mat('data.mat') # Clamp the left side mask1 = x == np.amin(x) vals1 = 0 # Get the element matric keyword arguments elem_dict = dict(D=STEEL_D) # We apply a downwards nodal force on the right side bottom_force = -5e7 mask2 = x == np.amax(x) A_n = calc_hat_area(y[mask2]) vals2 = np.zeros((A_n.size, 2)) vals2[:, 1] = A_n * bottom_force x_displacement, y_displacement = fem.FEM(x, y, simplices, create_element_matrix, func_source, mask1, vals1, mask2, vals2, elem_dict=elem_dict) x_new = x + x_displacement y_new = y + y_displacement fig, ax = plt.subplots() ax.triplot(x, y, simplices, color='r') ax.triplot(x_new, y_new, simplices, color='b') triangles_before = mesh.all_triangles(simplices, x, y) triangles_after = mesh.all_triangles(simplices, x_new, y_new) area_before = np.sum(fem.calc_areas(triangles_before)) area_after = np.sum(fem.calc_areas(triangles_after)) print(area_before) print(area_after) plt.show()
def ex_squares(): poissons = [0.1, 0.3, 0.5] max_areas = [0.5, 0.1, 0.01] contour = [np.array([[0, 0], [6, 0], [6, 2], [0, 2]])] save_file = 'squares' poissons2, max_areas2 = np.meshgrid(poissons, max_areas) num = 100 min_load = 0 max_load = -5e7 areas = np.zeros((num, *poissons2.shape)) loads = np.linspace(min_load, max_load, num=num) bar = Bar('simulating', max=areas.size) for i in range(poissons2.shape[0]): for j in range(poissons2.shape[1]): x, y, simplices = mesh.generate_and_import_mesh( contour, max_area=max_areas2[i, j]) elem_dict = dict(D=D(nu=poissons2[i, j])) mask1 = x == np.amin(x) vals1 = 0 # We apply a downwards nodal force on the right side mask2 = x == np.amax(x) A_n = calc_hat_area(y[mask2]) vals2 = np.zeros((A_n.size, 2)) vals2[:, 1] = A_n K = fem.assemble_global_matrix(x, y, simplices, create_element_matrix, elem_dict=elem_dict) f = fem.create_f_vector(x, y, simplices, func_source) K, f = fem.add_point_boundary(K, f, mask1, vals1) # bar = Bar('Simulating', max=num) for n, load in enumerate(loads): vals = vals2 * load f = fem.create_f_vector(x, y, simplices, func_source) f = fem.add_to_source(f, mask2, vals) u = np.linalg.solve(K, f) x_disp, y_disp = fem.unpack_u(u) x_new = x + x_disp y_new = y + y_disp triangles = mesh.all_triangles(simplices, x_new, y_new) area = np.sum(fem.calc_areas(triangles)) areas[n, i, j] = area np.save(save_file, areas) bar.next() bar.finish()
def ex_resolution(max_max_area=2, min_max_area=0.01, num=50): contour = [np.array([[0, 0], [6, 0], [6, 2], [0, 2]])] end_area = np.zeros(num) end_positions = np.zeros((num, 2)) bottom_traction = -5e7 max_areas = np.logspace(np.log2(max_max_area), np.log2(min_max_area), num=num, base=2) savefile = 'areas' savefile2 = 'positions' bar = Bar('Simulate', max=num) for i, area in enumerate(max_areas): x, y, simplices = mesh.generate_and_import_mesh(contour, max_area=area) bottom_right_index = (x == np.amax(x)) * (y == np.amin(y)) mask1 = x == np.amin(x) vals1 = 0 # Get the element matric keyword arguments elem_dict = dict(D=STEEL_D) # We apply a downwards nodal force on the right side mask2 = x == np.amax(x) A_n = calc_hat_area(y[mask2]) vals2 = np.zeros((A_n.size, 2)) vals2[:, 1] = A_n * bottom_traction x_displacement, y_displacement = fem.FEM(x, y, simplices, create_element_matrix, func_source, mask1, vals1, mask2, vals2, elem_dict=elem_dict) x_new = x + x_displacement y_new = y + y_displacement triangles_after = mesh.all_triangles(simplices, x_new, y_new) end_area[i] = np.sum(fem.calc_areas(triangles_after)) end_positions[i, :] = [ x_new[bottom_right_index], y_new[bottom_right_index] ] np.save(savefile, end_area) np.save(savefile2, end_positions) bar.next() bar.finish()
def calc_cv_areas(x, y, simplices): """ Calculates the nodal "area" - the area of each control volume, for a median dual vertex centred control volume. """ # For this type of control volume, each triangular element is split up into # three parts of equal size. The size of the control volume is then the # total area of all triangles the vertex is a part of, divided by 3. m = np.zeros(x.shape) triangles = mesh.all_triangles(simplices, x, y) areas = mesh.calc_areas(triangles) for i in range(x.size): neighbours = mesh.find_neighbouring_simplices(simplices, i) m[i] = np.sum(areas[neighbours]) / 3 return m
def ex_load(min_load=0, max_load=-5e7, num=100, max_area=0.01): contour = [np.array([[0, 0], [6, 0], [6, 2], [0, 2]])] x, y, simplices = mesh.generate_and_import_mesh(contour, max_area=max_area) start_area = 12 savefile = 'loads' areas = np.zeros(num) loads = np.linspace(min_load, max_load, num=num) mask1 = x == np.amin(x) vals1 = 0 # Get the element matric keyword arguments elem_dict = dict(D=STEEL_D) # We apply a downwards nodal force on the right side mask2 = x == np.amax(x) A_n = calc_hat_area(y[mask2]) vals2 = np.zeros((A_n.size, 2)) vals2[:, 1] = A_n K = fem.assemble_global_matrix(x, y, simplices, create_element_matrix, elem_dict=elem_dict) f = fem.create_f_vector(x, y, simplices, func_source) K, f = fem.add_point_boundary(K, f, mask1, vals1) # bar = Bar('Simulating', max=num) for i, load in enumerate(loads): vals = vals2 * load f = fem.create_f_vector(x, y, simplices, func_source) f = fem.add_to_source(f, mask2, vals) u = np.linalg.solve(K, f) x_disp, y_disp = fem.unpack_u(u) x_new = x + x_disp y_new = y + y_disp triangles = mesh.all_triangles(simplices, x_new, y_new) area = np.sum(fem.calc_areas(triangles)) areas[i] = area np.save(savefile, [loads, areas])
def create_f_vector(x, y, simplices, func_source, d=2, source_dict=dict()): """ Creates the source vector f for the FEM system Arguments: - x, y: (n,) array of vertex positions - simplices: (n, 3) array of vertex numbers that make up each element - func_source: function that calculates the element matrix for a given element, must have signature: func_source(tri, **kwargs) - d: dimensionality of the output space (ie, scalar field or vector field) - source_dict: keyword arguments passed to func_source Returns: - f: (nd, ) Source term vector for the system """ triangles = mesh.all_triangles(simplices, x, y) f = np.zeros(d * x.size) for tri, simplex in zip(triangles, simplices): ind = get_global_indices(simplex, d) f_tri = func_source(tri, **source_dict) f[ind] += f_tri return f
def ex_show_loads(): contour = [np.array([[0, 0], [6, 0], [6, 2], [0, 2]])] x, y, simplices = mesh.generate_and_import_mesh(contour, max_area=0.01) load = -5e7 mask1 = x == np.amin(x) vals1 = 0 # Get the element matric keyword arguments elem_dict = dict(D=STEEL_D) # We apply a downwards nodal force on the right side mask2 = x == np.amax(x) A_n = calc_hat_area(y[mask2]) vals2 = np.zeros((A_n.size, 2)) vals2[:, 1] = A_n K = fem.assemble_global_matrix(x, y, simplices, create_element_matrix, elem_dict=elem_dict) f = fem.create_f_vector(x, y, simplices, func_source) K, f = fem.add_point_boundary(K, f, mask1, vals1) vals = vals2 * load f = fem.add_to_source(f, mask2, vals) u = np.linalg.solve(K, f) x_disp, y_disp = fem.unpack_u(u) x_new = x + x_disp y_new = y + y_disp fig, ax = plt.subplots() ax.triplot(x, y, simplices) ax.triplot(x_new, y_new, simplices) triangles = mesh.all_triangles(simplices, x_new, y_new) area = np.sum(fem.calc_areas(triangles)) print(area) plt.show()
def ex_debug(): X = np.array((0, 1, 0.5)) Y = np.array((0, 0, np.sqrt(3) / 2)) T = np.array((0, 1, 2)).reshape((1, 3)) I = np.array((X.sum(), Y.sum())) / 3 tris = mesh.all_triangles(T, X, Y) area = mesh.calc_areas(tris) a = 1.1 i = a * I x = a * X y = a * Y v = I - i x = x + v[0] y = y + v[1] i = i + v X2 = X - I[1] Y2 = Y - I[1] # lims = np.array(((np.amin(x), np.amax(x)), (np.amin(y), np.amax(y)))) E, nu = 1e3, 0.3 rho = 10 b = np.zeros(2) t = b mask = np.zeros(3) == 1 bmask = ~mask cvs = None De0inv, m, f_ext, ft = proj.calc_intial_stuff(X, Y, T, b, rho, mask, t) m = m.reshape((3, 1)) lambda_, mu = proj.calc_lame_parameters(E, nu) fe = proj.calc_all_fe(x, y, T, cvs, De0inv, lambda_, mu) k = 1 / 2 * (a * a + 1) * (lambda_ + mu) T0 = 1 K = 5e-3 dt = K * np.sqrt(rho / E) N = np.ceil(T0 / dt).astype(int) v = np.zeros((3, 2)) p = np.array((x, y)).T points = np.zeros((N, 3, 2)) E_kin = np.zeros(N) E_pot = np.zeros(N) E_str = np.zeros(N) E_kin[0] = proj.calc_kin_energy(m, v) E_pot[0] = calc_pot_energy_tri(np.array((x[0], y[0])), np.zeros(2), k) E_pot[0] = proj.calc_pot_energy(m, y) E_str[0] = proj.calc_strain_energy(x, y, T, De0inv, lambda_, mu, area) points[0] = p fes = np.zeros((N, 3, 2)) bar = Bar('Simulating', max=N) bar.next() for n in range(1, N): x, y = points[n - 1].T fe = proj.calc_all_fe(x, y, T, cvs, De0inv, lambda_, mu) f_total = fe fes[n] = f_total points[n], v = proj.calc_next_time_step(points[n - 1], v, m, f_total, dt, bmask) E_kin[n] = proj.calc_kin_energy(m, v) x, y = points[n].T E_str[n] = proj.calc_strain_energy(x, y, T, De0inv, lambda_, mu, area) E_pot[n] = calc_pot_energy_tri(np.array((x[0], y[0])), np.zeros(2), k) E_pot[n] = proj.calc_pot_energy(m, y) bar.next() bar.finish() N_frames = 500 if N < N_frames: frame_skip = 1 else: frame_skip = np.floor(N / N_frames).astype(int) x_all = points[:, :, 0].flatten() y_all = points[:, :, 1].flatten() xmax = np.amax(x_all) xmin = np.amin(x_all) ymin = np.amin(y_all) ymax = np.amax(y_all) limits = np.array(((xmin, xmax), (ymin, ymax))) outfile = 'triangle4.mp4' dpi = 200 fps = 60 padding = 0.2 xlims, ylims = limits padding = np.array((-padding, padding)) xlims = xlims + padding ylims = ylims + padding Times = np.cumsum(dt * np.ones(N)) fig, (ax, ax2) = plt.subplots(nrows=2, gridspec_kw={'height_ratios': [2, 1]}) ax2.plot(Times, E_kin + E_pot + E_str, label='$E_{total}$') ax2.plot(Times, E_pot, label='$E_{pot}$') ax2.plot(Times, E_kin, label='$E_{kin}$') ax2.plot(Times, E_str, label='$E_{str}$') ax2.legend() plt.show()
def simulate(x, y, simplices, cvs, dt=1, N=10, lambda_=1, mu=1, b=np.zeros(2), t=np.array((0, -1)), rho=1, t_mask=None, boundary_mask=None, y0=None, T_stopt=None): """ Simulates the system Inputs: - x, y: (n,) arrays of nodal positions - simlices: (m,3) connectivity matrix - cvs: n-list of control volumes - dt: float, time step - N: total steps in the simulation. The initial position is the first step, so only N-1 steps are simulated - lambda_, mu: Lame parameters - b: body force density - t: traction - rho: mass density of the system - t_mask: (n,) boolean array of vertices to apply traction to. if None, apply traction to right boundary - boundary_mask: (n,) boolean array of nodes to update, ie NOT the clamped boundary. If None, clamp left edge, ie. False on left edge Outputs: - points_t: (N,n,2) array of vertex positions for each step. """ if boundary_mask is None: boundary_mask = x != np.amin(x) if t_mask is None: t_mask = x == np.amax(x) n_p = -1 points = np.array((x, y)).T areas = mesh.calc_areas(mesh.all_triangles(simplices, x, y)) v = np.zeros(points.shape) points_t = np.zeros((N, *points.shape)) E_kin = np.zeros(N) E_pot = np.zeros(N) E_str = np.zeros(N) momentum = np.zeros((N, 2)) De0inv, m, f_ext, ft = calc_intial_stuff(x, y, simplices, b, rho, t_mask, t) m = m.reshape((m.size, 1)) points_t[0] = points h = y0 if y0 is not None else 0 E_kin[0] = calc_kin_energy(m, v) E_pot[0] = calc_pot_energy(m, y, h) E_str[0] = calc_strain_energy(x, y, simplices, De0inv, lambda_, mu, areas) momentum[0] = calc_momentum(m, v) bar = Bar('simulating', max=N) bar.next() for n in range(1, N): if y0 is not None: points_t[n - 1], v = floor_(points_t[n - 1], y0=y0, v=v) T = dt * n if T_stopt <= T and T_stopt is not None: # print('stop T') ft = 0 x, y = points_t[n - 1].T fe = calc_all_fe(x, y, simplices, cvs, De0inv, lambda_, mu, n=True if n == n_p else False) f_total = ft + fe + f_ext points_t[n], v = calc_next_time_step(points_t[n - 1], v, m, f_total, dt, boundary_mask) momentum[n] = calc_momentum(m, v) E_kin[n] = calc_kin_energy(m, v) x, y = points_t[n].T E_pot[n] = calc_pot_energy(m, y, h) E_str[n] = calc_strain_energy(x, y, simplices, De0inv, lambda_, mu, areas) bar.next() bar.finish() return points_t, E_pot, E_kin, E_str, momentum
def create_control_volumes(x, y, simplices): def _calc_for_control(ox, oy, dx, dy): # Calculate stuff for create_control_volumes l = np.sqrt((dx-ox)**2 + (dy-oy)**2) ex = (dx - ox)/l ey = (dy - oy)/l nx = -ey ny = ex mx = (dx + ox)/2 my = (dy + oy)/2 return l, ex, ey, nx, ny, mx, my N = x.size triangles = mesh.all_triangles(simplices, x, y) incenters = calc_incenters(triangles) cx, cy = incenters.T points = np.array((x,y)) hull = spatial.ConvexHull(points.T) hull_vertices = hull.vertices boundary_mask = np.zeros(N) boundary_mask[hull_vertices] = 1 cvs = [] for i in range(N): indices = mesh.find_neighbouring_simplices(simplices, i) K = indices.size I = [] N = [] OX = [] OY = [] DX = [] DY = [] L = [] EX = [] EY = [] NX = [] NY = [] MX = [] MY = [] code = [] if boundary_mask[i]: a = simplices[indices[0], 0] b = simplices[indices[0], 1] c = simplices[indices[0], 2] ii, jj, kk = find_vertex_order(i, a, b, c) ox = x[i] oy = y[i] dx, dy = project_to_edge(ox, oy, x[jj], y[jj], cx[indices[0]], cy[indices[0]]) print(f'ox: {ox:.2f}, oy: {oy:.2f}') print(f'dx: {dx:.2f}, dy: {dy:.2f}') l, ex, ey, nx, ny, mx, my = _calc_for_control(ox, oy, dx, dy) I.append(i) N.append(-1) OX.append(ox) OY.append(oy) DX.append(dx) DY.append(dy) L.append(l) EX.append(ex) EY.append(ey) NX.append(-nx) NY.append(-ny) MX.append(mx) MY.append(my) code.append(2) ox = dx oy = dy dx = cx[indices[0]] dy = cy[indices[0]] l, ex, ey, nx, ny, mx, my = _calc_for_control(ox, oy, dx, dy) I.append(i) N.append(jj) OX.append(ox) OY.append(oy) DX.append(dx) DY.append(dy) L.append(l) EX.append(ex) EY.append(ey) NX.append(-nx) NY.append(-ny) MX.append(mx) MY.append(my) code.append(1) lastK = K-1 if boundary_mask[i] else K for j in range(lastK): a = simplices[indices[j], 0] b = simplices[indices[j], 1] c = simplices[indices[j], 2] ii, jj, kk = find_vertex_order(i, a, b, c) # Origin vertex index o = indices[j] # Destination vertex index d = indices[(j+1)%K] # print(f'j: {j}, j mod: {(j+1)%K}') ox = cx[o] oy = cy[o] dx = cx[d] dy = cy[d] l, ex, ey, nx, ny, mx, my = _calc_for_control(ox, oy, dx, dy) I.append(i) N.append(kk) OX.append(ox) OY.append(oy) DX.append(dx) DY.append(dy) L.append(l) EX.append(ex) EY.append(ey) NX.append(-nx) NY.append(-ny) MX.append(mx) MY.append(my) code.append(0) if boundary_mask[i]: # index -1 corresponds to "K" in matlab code a = simplices[indices[-1], 0] b = simplices[indices[-1], 1] c = simplices[indices[-1], 2] ii, jj, kk = find_vertex_order(i, a, b, c) ox = cx[indices[-1]] oy = cy[indices[-1]] dx, dy = project_to_edge(x[kk], y[kk], x[i], y[i], ox, oy) l, ex, ey, nx, ny, mx, my = _calc_for_control(ox, oy, dx, dy) I.append(i) N.append(kk) OX.append(ox) OY.append(oy) DX.append(dx) DY.append(dy) L.append(l) EX.append(ex) EY.append(ey) NX.append(-nx) NY.append(-ny) MX.append(mx) MY.append(my) code.append(1) ox = dx ox = dy dx = x[i] dy = y[i] l, ex, ey, nx, ny, mx, my = _calc_for_control(ox, oy, dx, dy) I.append(i) N.append(-1) OX.append(ox) OY.append(oy) DX.append(dx) DY.append(dy) L.append(l) EX.append(ex) EY.append(ey) NX.append(-nx) NY.append(-ny) MX.append(mx) MY.append(my) code.append(2) cv = {'I':np.array(I), 'N':np.array(N), 'ox':np.array(OX), 'oy':np.array(OY), 'dx':np.array(DX), 'dy':np.array(DY), 'l':np.array(L), 'ex':np.array(EX), 'ey':np.array(EY), 'nx':np.array(NX), 'ny':np.array(NY), 'mx':np.array(MX), 'my':np.array(MY), 'code':np.array(code)} cvs.append(cv) return cvs
def calc_phi_on_centroids(x, y, simplices, phi): triangles = mesh.all_triangles(simplices, x, y) phi_tri = phi[simplices] phi_tri_avg = np.mean(phi_tri, axis=1) return triangles, phi_tri_avg