""" Code for modeling hard-magnetic magneto-viscoelastic snap-through. - with the model comprising: > Large deformation compressible Neo-Hookean elasticity > Finite deformation viscoelasticity through a generalized Maxwell model with 3 branches, with evolution of internal variables in the style of Green and Tobolsky (1946) and Linder et al. (2011). > Inertial effects using the Newmark kinematic relations. > Magnetic stresses due to magneto-quasistatic interactions between - Permanently magnetized particles (m_rem = b_rem/mu0) embedded in a mechanically soft matrix, and - an externally applied magnetic flux density (b_app). - Under suitable assumptions described in the paper, magneto-quasistatics is satisfied automatically and the only necessary numerical degree of freedom is the displacement vector u. - To aid in modeling the near-incompressibility we also introduce a pressure-like DOF p which satisfies (J-1) - p/K = 0. > This is the classical (u,p) approach. - Basic units: > Length: mm > Time: s > Mass: kg > Charge: kC - Derived units: > Force: mN > Pressure: kPa > Current: kA > Mag. flux density: mT Eric M. Stewart (ericstew@mit.edu) Spring 2023 """ # FEniCS package #import dolfin.fem as fem, ufl from dolfin import * # NumPy for arrays and array operations import numpy as np # MatPlotLib for plotting import matplotlib.pyplot as plt # Current time package from datetime import datetime #from dolfinx.interpolation import interpolate as dl_interp # Set level of detail for log messages (integer) # # Guide: # CRITICAL = 50, // errors that may lead to data corruption # ERROR = 40, // things that HAVE gone wrong # WARNING = 30, // things that MAY go wrong later # INFO = 20, // information of general interest (includes solver info) # PROGRESS = 16, // what's happening (broadly) # TRACE = 13, // what's happening (in detail) # DBG = 10 // sundry # set_log_level(30) # Global FEniCS parameters: # Optimization options for the form compiler parameters["form_compiler"]["cpp_optimize"] = True parameters["form_compiler"]["representation"] = "quadrature" parameters["form_compiler"]["cpp_optimize_flags"] = "-O3 -ffast-math -march=native" parameters["form_compiler"]["quadrature_degree"] = 3 ''''''''''''''''''''' DEFINE GEOMETRY ''''''''''''''''''''' # Overall dimensions of rectangular prism device scaleX = 120.47 #60.26 # mm scaleY = 2.5 # mm scaleZ = 20.0 # mm # N number of elements in each direction Xelem = 50 Yelem = 4 Zelem = 4 # Define a uniformly spaced box mesh mesh = BoxMesh(Point(0.0, 0.0, 0.0),Point(scaleX,scaleY, scaleZ),Xelem, Yelem, Zelem) # Add an initial imperfection to control buckling mode imperf = 0.01 # mm # Map the coordinates of the uniform box mesh to the biased spacing xOrig = mesh.coordinates() xMap1 = np.zeros((len(xOrig),3)) # Mapping functions for i in range(0,len(xMap1)): xMap1[i,0] = xOrig[i,0] xMap1[i,1] = xOrig[i,1] + (imperf/2.0)*(1.0 - np.cos(2*np.pi*xOrig[i,0]/scaleX)) xMap1[i,2] = xOrig[i,2] mesh.coordinates()[:] = xMap1 # This says "spatial coordinates" but is really the referential coordinates, # since the mesh does not convect in FEniCS. x = SpatialCoordinate(mesh) ''''''''''''''''''''' SUBDOMAINS ''''''''''''''''''''' #---------------------------------------------------------- # Define the mesh subdomains, used for applying BCs and # spatially-varying material properties. tol = 1e-12 #Pick up on the boundary entities of the created mesh class Left(SubDomain): def inside(self, x, on_boundary): return near(x[0],0.0, tol) and on_boundary class Right(SubDomain): def inside(self, x, on_boundary): return near(x[0],scaleX, tol) and on_boundary class Bottom(SubDomain): def inside(self, x, on_boundary): return near(x[1], 0.0, tol) and on_boundary class Top(SubDomain): def inside(self, x, on_boundary): return near(x[1],scaleY, tol) and on_boundary class Back(SubDomain): def inside(self, x, on_boundary): return near(x[2],0.0, tol) and on_boundary class Front(SubDomain): def inside(self, x, on_boundary): return near(x[2],scaleZ, tol) and on_boundary # Mark boundary subdomians facets = MeshFunction("size_t", mesh, 2) facets.set_all(0) DomainBoundary().mark(facets, 1) # First, mark all boundaries with common index # Next mark sepcific boundaries Left().mark(facets, 2) Right().mark(facets,3) Bottom().mark(facets, 4) Top().mark(facets,5) Back().mark(facets, 6) Front().mark(facets,7) # Define a ds measure for each face, necessary for applying traction BCs. ds = Measure('ds', domain=mesh, subdomain_data=facets) ''''''''''''''''''''' MATERIAL PARAMETERS ''''''''''''''''''''' # Gent elasticity Gshear0 = Constant(1400.0) # kPa Kbulk = Constant(10*Gshear0) # Nearly-incompressible # Mass density rho = Constant(2.000e-6) # 1.75e3 kg/m^3 = 1.75e-6 kg/mm^3 # Vacuum permeability mu0 = Constant(1.256e-6*1e9) # mN / mA^2 # Remanent magnetic flux density vector b_rem_mag = 67.5 # mT # Spatially-varying remanent magnetization b_rem = Expression(("(x[0]T_tot: # step = "Snap" dt = step2_dt dk.assign(dt) t += dt #b_app_mag.t = t - T_tot b_app_mag.t = 1.0 else: # updates for first phase: t += dt # update time-varying BCs disp_exp.t = t step = "Buckle" # increment time, counter ii = ii + 1 # Solve the problem (iter, converged) = solver.solve() # Write results to *.xdmf at current time writeResults(t) # Output time histories if t+dt>T_tot: b_out[ii] = b_app_max*(t-T_tot)/step2_time else: b_out[ii] = 0.0 disp_out[ii] = w(scaleX/2, scaleY/2, scaleZ/2)[1] time_out[ii] = t # Update fields for next step u_proj = project(u, W2) u_proj_old = project(u_old, W2) update_fields(u_proj, u_proj_old, v_old, a_old) w_old.vector()[:] = w.vector() # Update state variables A1_old.assign(local_project_intvar(A1, W3)) #A1_old. assign(A1) #A1_old.interpolate(A1) #A1_old.vector()[:] = A1.vector() #A1_old.interpolate(A1) #A1_old.vector()[:] = A1.vector() #A1_old.assign(project(A1)) # Print progress of calculation if ii%5==0: now = datetime.now() current_time = now.strftime("%H:%M:%S") print("Step: {} | Simulation Time: {} s | Iterations: {}".format(step, t, iter)) print() ''''''''''''''''''''' VISUALIZATION ''''''''''''''''''''' # Set up font size, initialize colors array font = {'size' : 16} plt.rc('font', **font) # prop_cycle = plt.rcParams['axes.prop_cycle'] colors = prop_cycle.by_key()['color'] # only plot as far as time_out has time history for. ind = np.argmax(time_out) ind2 = np.where(time_out==T_tot)[0][0] expData = np.genfromtxt('Tan_B_step_7mT_data.csv', delimiter=',') plt.figure() plt.scatter(expData[:,0] - expData[0,0], expData[:,1], s=25, edgecolors=(0.0, 0.0, 0.0,1), color=(1, 1, 1, 1), label='Experiment', linewidth=1.0) plt.plot(time_out[0:ind]-T_tot, -(disp_out[0:ind]-disp_out[ind2]), linewidth=2.5, \ color=colors[3], label='Simulation') plt.axvline(0, c='k', linewidth=1.) plt.axhline(0, c='k', linewidth=1.) plt.axis('tight') plt.xlabel(r"Time (s)") plt.ylabel(r"$u_2^\mathrm{midspan}$ (mm)") plt.grid(linestyle="--", linewidth=0.5, color='b') plt.ylim(-0.0, 12) plt.xlim(-0.0,step2_time) plt.legend() # save figure to file fig = plt.gcf() fig.set_size_inches(6, 4) plt.tight_layout() plt.savefig("hard_magnetic_constant_bfield-quadrature.png", dpi=600)