class TLineMixIn(tr.HasTraits): #========================================================================= # TIME LINE #========================================================================= tline = bu.Instance(TLine) r'''Time line defining the time range, discretization and state, ''' def _tline_default(self): return TLine( time_change_notifier=self.time_changed, time_range_change_notifier=self.time_range_changed ) def time_changed(self, time): if not(self.ui is None): self.ui.time_changed(time) def time_range_changed(self, tmax): self.tline.max = tmax if not(self.ui is None): self.ui.time_range_changed(tmax) def set_tmax(self, time): self.time_range_changed(time) t = tr.DelegatesTo('tline', 'val') t_max = tr.DelegatesTo('tline', 'max')
class WBTessellation4P(bu.Model): name = 'WB Tessellation 4P' wb_cell = bu.Instance(WBCell4Param) def _wb_cell_default(self): wb_cell = WBCell4Param() self.update_wb_cell_params(wb_cell) return wb_cell tree = ['wb_cell'] plot_backend = 'k3d' n_phi_plus = bu.Int(5, GEO=True) n_x_plus = bu.Int(3, GEO=True) gamma = bu.Float(1.25, GEO=True) a = bu.Float(1000, GEO=True) a_high = bu.Float(2000) b = bu.Float(1000, GEO=True) b_high = bu.Float(2000) c = bu.Float(1000, GEO=True) c_high = bu.Float(2000) show_wireframe = bu.Bool(True, GEO=True) show_nodes = bu.Bool(False, GEO=True) show_node_labels = bu.Bool(False, GEO=True) WIREFRAME = 'k3d_mesh_wireframe' NODES = 'k3d_nodes' NODES_LABELS = 'k3d_nodes_labels' @tr.observe('+GEO', post_init=True) def update_wb_cell(self, event): self.update_wb_cell_params(self.wb_cell) def update_wb_cell_params(self, wb_cell): wb_cell.trait_set( gamma=self.gamma, a=self.a, a_high=self.a_high, b=self.b, b_high=self.b_high, c=self.c, c_high=self.c_high, ) ipw_view = bu.View( # bu.Item('wb_cell'), *WBCell4Param.ipw_view.content, bu.Item('n_phi_plus', latex=r'n_\phi'), bu.Item('n_x_plus', latex=r'n_x'), # bu.Item('show_wireframe'), # bu.Item('show_node_labels'), bu.Item('show_nodes'), ) def get_phi_range(self, delta_phi): return np.arange(-(self.n_phi_plus - 1), self.n_phi_plus) * delta_phi def get_X_phi_range(self, delta_phi, R_0): """Given an array of angles and radius return an array of coordinates """ phi_range = self.get_phi_range((delta_phi)) return np.array([ np.fabs(R_0) * np.sin(phi_range), np.fabs(R_0) * np.cos(phi_range) + R_0 ]).T def get_X_x_range(self, delta_x): return np.arange(-(self.n_x_plus - 1), self.n_x_plus) * delta_x cell_map = tr.Property def _get_cell_map(self): delta_x = self.wb_cell.delta_x delta_phi = self.wb_cell.delta_phi R_0 = self.wb_cell.R_0 X_x_range = self.get_X_x_range(delta_x) X_phi_range = self.get_X_phi_range(delta_phi, R_0) n_idx_x = len(X_x_range) n_idx_phi = len(X_phi_range) idx_x = np.arange(n_idx_x) idx_phi = np.arange(n_idx_phi) idx_x_ic = idx_x[(n_idx_x) % 2::2] idx_x_id = idx_x[(n_idx_x + 1) % 2::2] idx_phi_ic = idx_phi[(n_idx_phi) % 2::2] idx_phi_id = idx_phi[(n_idx_phi + 1) % 2::2] n_ic = len(idx_x_ic) * len(idx_phi_ic) n_id = len(idx_x_id) * len(idx_phi_id) n_cells = n_ic + n_id return n_cells, n_ic, n_id, idx_x_ic, idx_x_id, idx_phi_ic, idx_phi_id n_cells = tr.Property def _get_n_cells(self): n_cells, _, _, _, _, _, _ = self.cell_map return n_cells X_cells_Ia = tr.Property(depends_on='+GEO') '''Array with nodal coordinates of uncoupled cells I - node, a - dimension ''' @tr.cached_property def _get_X_cells_Ia(self): delta_x = self.wb_cell.delta_x delta_phi = self.wb_cell.delta_phi R_0 = self.wb_cell.R_0 X_Ia_wb_rot = np.copy(self.wb_cell.X_Ia) X_Ia_wb_rot[..., 2] -= R_0 X_cIa = np.array([X_Ia_wb_rot], dtype=np.float_) rotation_axes = np.array([[1, 0, 0]], dtype=np.float_) rotation_angles = self.get_phi_range(delta_phi) q = axis_angle_to_q(rotation_axes, rotation_angles) X_dIa = qv_mult(q, X_cIa) X_dIa[..., 2] += R_0 X_x_range = self.get_X_x_range(delta_x) X_phi_range = self.get_X_phi_range(delta_phi, R_0) n_idx_x = len(X_x_range) n_idx_phi = len(X_phi_range) idx_x = np.arange(n_idx_x) idx_phi = np.arange(n_idx_phi) idx_x_ic = idx_x[(n_idx_x) % 2::2] idx_x_id = idx_x[(n_idx_x + 1) % 2::2] idx_phi_ic = idx_phi[(n_idx_phi) % 2::2] idx_phi_id = idx_phi[(n_idx_phi + 1) % 2::2] X_E = X_x_range[idx_x_ic] X_F = X_x_range[idx_x_id] X_CIa = X_dIa[idx_phi_ic] X_DIa = X_dIa[idx_phi_id] expand = np.array([1, 0, 0]) X_E_a = np.einsum('i,j->ij', X_E, expand) X_ECIa = X_CIa[np.newaxis, :, :, :] + X_E_a[:, np.newaxis, np.newaxis, :] X_F_a = np.einsum('i,j->ij', X_F, expand) X_FDIa = X_DIa[np.newaxis, :, :, :] + X_F_a[:, np.newaxis, np.newaxis, :] X_Ia = np.vstack( [X_ECIa.flatten().reshape(-1, 3), X_FDIa.flatten().reshape(-1, 3)]) return X_Ia I_cells_Fi = tr.Property(depends_on='+GEO') '''Array with nodal coordinates I - node, a - dimension ''' @tr.cached_property def _get_I_cells_Fi(self): I_Fi_cell = self.wb_cell.I_Fi n_I_cell = self.wb_cell.n_I n_cells = self.n_cells i_range = np.arange(n_cells) * n_I_cell I_Fi = (I_Fi_cell[np.newaxis, :, :] + i_range[:, np.newaxis, np.newaxis]).reshape(-1, 3) return I_Fi X_Ia = tr.Property(depends_on='+GEO') '''Array with nodal coordinates I - node, a - dimension ''' @tr.cached_property def _get_X_Ia(self): idx_unique, idx_remap = self.unique_node_map return self.X_cells_Ia[idx_unique] I_Fi = tr.Property(depends_on='+GEO') '''Facet - node mapping ''' @tr.cached_property def _get_I_Fi(self): _, idx_remap = self.unique_node_map return idx_remap[self.I_cells_Fi] node_match_threshold = tr.Property(depends_on='+GEO') def _get_node_match_threshold(self): min_length = np.min([self.a, self.b, self.c]) return min_length * 1e-4 unique_node_map = tr.Property(depends_on='+GEO') '''Property containing the mapping between the crease pattern nodes with duplicate nodes and pattern with compressed nodes array. The criterion for removing a node is geometric, the threshold is specified in node_match_threshold. ''' def _get_unique_node_map(self): # reshape the coordinates in array of segments to the shape (n_N, n_D x_0 = self.X_cells_Ia # construct distance vectors between every pair of nodes x_x_0 = x_0[:, np.newaxis, :] - x_0[np.newaxis, :, :] # calculate the distance between every pair of nodes dist_0 = np.sqrt(np.einsum('...i,...i', x_x_0, x_x_0)) # identify those at the same location zero_dist = dist_0 < self.node_match_threshold # get their indices i_idx, j_idx = np.where(zero_dist) # take only the upper triangle indices upper_triangle = i_idx < j_idx idx_multi, idx_delete = i_idx[upper_triangle], j_idx[upper_triangle] # construct a boolean array with True at valid and False at deleted # indices idx_unique = np.ones((len(x_0), ), dtype='bool') idx_unique[idx_delete] = False # Boolean array of nodes to keep - includes both those that # are unique and redirection nodes to be substituted for duplicates idx_keep = np.ones((len(x_0), ), dtype=np.bool_) idx_keep[idx_delete] = False # prepare the enumeration map map ij_map = np.ones_like(dist_0, dtype=np.int_) + len(x_0) i_ = np.arange(len(x_0)) # indexes of nodes that are being kept idx_row = i_[idx_keep] # enumerate the kept nodes by putting their number onto the diagonal ij_map[idx_keep, idx_keep] = np.arange(len(idx_row)) # broadcast the substitution nodes into the interaction positions ij_map[i_idx, j_idx] = ij_map[i_idx, i_idx] # get the substitution node by picking up the minimum index within ac column idx_remap = np.min(ij_map, axis=0) return idx_unique, idx_remap I_CDij = tr.Property(depends_on='+GEO') @tr.cached_property def _get_I_CDij(self): n_cells, n_ic, n_id, _, x_cell_idx, _, y_cell_idx = self.cell_map x_idx, y_idx = x_cell_idx / 2, y_cell_idx / 2 n_x_, n_y_ = len(x_idx), len(y_idx) I_cell_offset = (n_ic + np.arange(n_x_ * n_y_).reshape( n_x_, n_y_)) * self.wb_cell.n_I I_CDij_map = (I_cell_offset.T[:, :, np.newaxis, np.newaxis] + self.wb_cell.I_boundary[np.newaxis, np.newaxis, :, :]) return I_CDij_map def setup_plot(self, pb): self.pb = pb X_Ia = self.X_Ia.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) I_M = self.I_CDij[(0, -1), :, (0, -1), :] _, idx_remap = self.unique_node_map J_M = idx_remap[I_M] X_Ma = X_Ia[J_M.flatten()] k3d_mesh = k3d.mesh(X_Ia, I_Fi, color=0x999999, side='double') pb.objects['k3d_mesh'] = k3d_mesh pb.plot_fig += k3d_mesh if self.show_nodes: self._add_nodes_to_fig(pb, X_Ma) if self.wb_cell.show_node_labels: self._add_nodes_labels_to_fig(pb, X_Ia) if self.show_wireframe: self._add_wireframe_to_fig(pb, X_Ia, I_Fi) def update_plot(self, pb): X_Ia = self.X_Ia.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) I_M = self.I_CDij[(0, -1), :, (0, -1), :] _, idx_remap = self.unique_node_map J_M = idx_remap[I_M] X_Ma = X_Ia[J_M.flatten()] mesh = pb.objects['k3d_mesh'] mesh.vertices = X_Ia mesh.indices = I_Fi if self.show_nodes: if self.NODES in pb.objects: pb.objects[self.NODES].positions = X_Ma else: self._add_nodes_to_fig(pb, X_Ma) else: if self.NODES in pb.objects: pb.clear_object(self.NODES) if self.show_wireframe: if self.WIREFRAME in pb.objects: wireframe = pb.objects[self.WIREFRAME] wireframe.vertices = X_Ia wireframe.indices = I_Fi else: self._add_wireframe_to_fig(pb, X_Ia, I_Fi) else: if self.WIREFRAME in pb.objects: pb.clear_object(self.WIREFRAME) if self.show_node_labels: if self.NODES_LABELS in pb.objects: pb.clear_object(self.NODES_LABELS) self._add_nodes_labels_to_fig(pb, X_Ia) else: if self.NODES_LABELS in pb.objects: pb.clear_object(self.NODES_LABELS) def _add_nodes_labels_to_fig(self, pb, X_Ia): text_list = [] for I, X_a in enumerate(X_Ia): k3d_text = k3d.text('%g' % I, tuple(X_a), label_box=False, size=0.8, color=0x00FF00) pb.plot_fig += k3d_text text_list.append(k3d_text) pb.objects[self.NODES_LABELS] = text_list def _add_wireframe_to_fig(self, pb, X_Ia, I_Fi): k3d_mesh_wireframe = k3d.mesh(X_Ia, I_Fi, color=0x000000, wireframe=True) pb.plot_fig += k3d_mesh_wireframe pb.objects[self.WIREFRAME] = k3d_mesh_wireframe def _add_nodes_to_fig(self, pb, X_Ma): k3d_points = k3d.points(X_Ma, point_size=300) pb.objects[self.NODES] = k3d_points pb.plot_fig += k3d_points def _show_or_hide_fig_object(self, pb, show_obj, obj_name, obj_add_fun, obj_update_fun): if show_obj: if obj_name in pb.objects: obj_update_fun() else: obj_add_fun() else: if obj_name in pb.objects: pb.clear_object(obj_name) def export_fold_file(self, path=None): # See https://github.com/edemaine/fold/blob/master/doc/spec.md for fold file specification # Viewer: https://edemaine.github.io/fold/examples/foldviewer.html output_data = { "file_spec": 1, "file_creator": "BMCS software suite", "file_author": "RWTH Aachen - Institute of Structural Concrete", "file_title": "Preliminary Base", "file_classes": ["singleModel"], "frame_title": "Preliminary Base Crease Pattern", "frame_classes": ["creasePattern"], "vertices_coords": self.X_Ia.tolist(), "faces_vertices": self.I_Fi.tolist(), # To be completed } if path is None: path = time.strftime("%Y%m%d-%H%M%S") + '-shell.fold' with open(path, 'w') as outfile: json.dump(output_data, outfile, sort_keys=True, indent=4)
class SlideExplorer(bu.Model): name = 'Explorer' tree = [ 'slide_model', 'inel_state_evolution', 'energy_dissipation', 'tf_s_x', 'tf_s_y', 'tf_w' ] slide_model = bu.Instance(Slide32, (), tree=True) # slide_model = bu.Instance(Slide34, (), tree=True) energy_dissipation = bu.Instance(EnergyDissipation, tree=True) '''Viewer to the energy dissipation''' def _energy_dissipation_default(self): return EnergyDissipation(slider_exp=self) inel_state_evolution = bu.Instance(InelStateEvolution, tree=True) '''Viewer to the inelastic state evolution''' def _inel_state_evolution_default(self): return InelStateEvolution(slider_exp=self) time_fn = bu.Instance(TimeFunction, (), tree=True) def __init__(self, *args, **kw): super(SlideExplorer, self).__init__(*args, **kw) self.reset_i() n_Eps = tr.Property() def _get_n_Eps(self): return len(self.slide_model.symb.Eps) s_x_1 = bu.Float(0, INC=True) s_y_1 = bu.Float(0, INC=True) w_1 = bu.Float(0, INC=True) tf_s_x = bu.Instance(TimeFunction, TIME=True) def _tf_s_x_default(self): return TFSelector() tf_s_y = bu.Instance(TimeFunction, TIME=True) def _tf_s_y_default(self): return TFSelector() tf_w = bu.Instance(TimeFunction, TIME=True) def _tf_w_default(self): return TFSelector() n_steps = bu.Int(10, ALG=True) k_max = bu.Int(20, ALG=True) Sig_arr = tr.Array Eps_arr = tr.Array Sig_t = tr.Property def _get_Sig_t(self): return self.Sig_arr Eps_t = tr.Property def _get_Eps_t(self): return self.Eps_arr ipw_view = bu.View(bu.Item('s_x_1', latex=r's_x'), bu.Item('s_y_1', latex=r's_y'), bu.Item('w_1', latex=r'w'), bu.Item('n_steps'), bu.Item('k_max'), bu.Item('t_max', readonly=True), time_editor=bu.ProgressEditor( run_method='run', reset_method='reset', interrupt_var='sim_stop', time_var='t', time_max='t_max', )) def reset_i(self): self.s_x_0, self.s_y_0, self.w_0 = 0, 0, 0 self.t0 = 0 self.t = 0 self.t_max = 1 self.Sig_arr = np.zeros((0, self.n_Eps)) self.Eps_arr = np.zeros((0, self.n_Eps)) self.Sig_record = [] self.Eps_record = [] self.iter_record = [] self.t_arr = [] self.s_x_t, self.s_y_t, self.w_t = [], [], [] self.Eps_n1 = np.zeros((self.n_Eps, ), dtype=np.float_) self.Sig_n1 = np.zeros((self.n_Eps, ), dtype=np.float_) self.s_x_1 = 0 self.s_y_1 = 0 self.w_1 = 0 t = bu.Float(0) t_max = bu.Float(1) def _t_max_changed(self): self.inel_state_evolution.t_max = self.t_max def get_response_i(self, update_progress=lambda t: t): # global Eps_record, Sig_record, iter_record # global t_arr, s_x_t, s_y_t, w_t, s_x_0, s_y_0, w_0, t0, Eps_n1 n_steps = self.n_steps t_i = np.linspace(0, 1, n_steps + 1) t1 = self.t0 + 1 self.t_max = t1 ti_arr = np.linspace(self.t0, t1, n_steps + 1) delta_t = t1 - self.t0 tf_s_x = self.tf_s_x(np.linspace(0, delta_t, n_steps + 1)) tf_s_y = self.tf_s_y(np.linspace(0, delta_t, n_steps + 1)) tf_w = self.tf_w(np.linspace(0, delta_t, n_steps + 1)) # si_x_t = tf_s_x * np.linspace(self.s_x_0, self.s_x_1, n_steps + 1) + 1e-9 # si_y_t = tf_s_y * np.linspace(self.s_y_0, self.s_y_1, n_steps + 1) + 1e-9 # wi_t = tf_w * np.linspace(self.w_0, self.w_1, n_steps + 1) + 1e-9 si_x_t = self.s_x_0 + tf_s_x * (self.s_x_1 - self.s_x_0) + 1e-9 si_y_t = self.s_y_0 + tf_s_y * (self.s_y_1 - self.s_y_0) + 1e-9 wi_t = self.w_0 + tf_w * (self.w_1 - self.w_0) + 1e-9 for i, (t, s_x_n1, s_y_n1, w_n1) in enumerate(zip(t_i, si_x_t, si_y_t, wi_t)): if self.slide_model.debug_level == 1: print('============= INCREMENT', i) try: self.Eps_n1, self.Sig_n1, k = self.slide_model.get_sig_n1( s_x_n1, s_y_n1, w_n1, self.Sig_n1, self.Eps_n1, self.k_max) except ConvergenceError as e: print(e) break self.Sig_record.append(self.Sig_n1) self.Eps_record.append(self.Eps_n1) self.iter_record.append(k) self.t = t self.Sig_arr = np.array(self.Sig_record, dtype=np.float_) self.Eps_arr = np.array(self.Eps_record, dtype=np.float_) self.iter_t = np.array(self.iter_record, dtype=np.int_) n_i = len(self.iter_t) self.t_arr = np.hstack([self.t_arr, ti_arr])[:n_i] self.s_x_t = np.hstack([self.s_x_t, si_x_t])[:n_i] self.s_y_t = np.hstack([self.s_y_t, si_y_t])[:n_i] self.w_t = np.hstack([self.w_t, wi_t])[:n_i] self.t0 = t1 self.s_x_0, self.s_y_0, self.w_0 = self.s_x_1, self.s_y_1, self.w_1 # set the last step index in the response browser self.inel_state_evolution.t_max = self.t_arr[-1] return # ## Plotting functions # To simplify postprocessing examples, here are two aggregate plotting functions, one for the state and force variables, the other one for the evaluation of energies def plot_sig_w(self, ax): sig_t = self.Sig_arr.T[2, ...] ax.plot(self.w_t, sig_t, color='orange', lw=3) def plot3d_Sig_Eps(self, ax3d): tau_x, tau_y = self.Sig_arr.T[:2, ...] tau = np.sqrt(tau_x**2 + tau_y**2) ax3d.plot3D(self.s_x_t, self.s_y_t, tau, color='orange', lw=2) def run(self, update_progress=lambda t: t): try: self.get_response_i(update_progress) except ValueError: print('No convergence reached') return def reset(self): self.reset_i() def subplots(self, fig): ax = fig.add_gridspec(1, 3) ax1 = fig.add_subplot(ax[0, 0:2], projection='3d') ax2 = fig.add_subplot(ax[0:, -1]) return ax1, ax2 def update_plot(self, axes): ax_sxy, ax_sig = axes self.plot_sig_w(ax_sig) ax_sig.set_xlabel(r'$w$ [mm]') ax_sig.set_ylabel(r'$\sigma$ [MPa]') # plot_tau_s(ax1, Eps_arr[-1,...],s_max,500,get_g3,**kw) ax_sxy.plot(self.s_x_t, self.s_y_t, 0, color='red', lw=1) self.plot3d_Sig_Eps(ax_sxy) ax_sxy.set_xlabel(r'$s_x$ [mm]') ax_sxy.set_ylabel(r'$s_y$ [mm]') ax_sxy.set_zlabel(r'$\| \tau \| = \sqrt{\tau_x^2 + \tau_y^2}$ [MPa]')
class WBShellAnalysis(TStepBC, bu.InteractiveModel): name = 'WBShellAnalysis' plot_backend = 'k3d' id = bu.Str """ if you saved boundary conditions for your current analysis, this id will make sure these bcs are loaded automatically next time you create an instance with the same id """ h = bu.Float(10, GEO=True) show_wireframe = bu.Bool(True, GEO=True) ipw_view = bu.View( bu.Item('h', editor=bu.FloatRangeEditor(low=1, high=100, n_steps=100), continuous_update=False), bu.Item('show_wireframe'), time_editor=bu.ProgressEditor(run_method='run', reset_method='reset', interrupt_var='interrupt', time_var='t', time_max='t_max'), ) n_phi_plus = tr.Property() def _get_n_phi_plus(self): return self.xdomain.mesh.n_phi_plus tree = ['geo', 'bcs', 'tmodel', 'xdomain'] geo = bu.Instance(WBShellGeometry4P, ()) tmodel = bu.Instance(MATS2DElastic, ()) # tmodel = bu.Instance(MATSShellElastic, ()) bcs = bu.Instance(BoundaryConditions) def _bcs_default(self): return BoundaryConditions(geo=self.geo, n_nodal_dofs=self.xdomain.fets.n_nodal_dofs, id=self.id) xdomain = tr.Property(tr.Instance(TriXDomainFE), depends_on="state_changed") '''Discretization object.''' @tr.cached_property def _get_xdomain(self): # prepare the mesh generator # mesh = WBShellFETriangularMesh(geo=self.geo, direct_mesh=False, subdivision=2) mesh = WBShellFETriangularMesh(geo=self.geo, direct_mesh=True) # construct the domain with the kinematic strain mapper and stress integrator return TriXDomainFE( mesh=mesh, integ_factor=self.h, ) # mesh = WBShellFETriangularMesh(geo=self.geo, direct_mesh=True) # mesh.fets = FETS2DMITC(a= self.h) # return TriXDomainMITC( # mesh=mesh # ) domains = tr.Property(depends_on="state_changed") @tr.cached_property def _get_domains(self): return [(self.xdomain, self.tmodel)] def reset(self): self.sim.reset() t = tr.Property() def _get_t(self): return self.sim.t def _set_t(self, value): self.sim.t = value t_max = tr.Property() def _get_t_max(self): return self.sim.t_max def _set_t_max(self, value): self.sim.t_max = value interrupt = tr.Property() def _get_interrupt(self): return self.sim.interrupt def _set_interrupt(self, value): self.sim.interrupt = value bc = tr.Property(depends_on="state_changed") # @tr.cached_property def _get_bc(self): bc_fixed, _, _ = self.bcs.bc_fixed bc_loaded, _, _ = self.bcs.bc_loaded return bc_fixed + bc_loaded def run(self): s = self.sim s.tloop.k_max = 10 s.tline.step = 1 s.tloop.verbose = False s.run() def get_max_vals(self): self.run() U_1 = self.hist.U_t[-1] U_max = np.max(np.fabs(U_1)) return U_max def export_abaqus(self): al = AbaqusLink(shell_analysis=self) al.model_name = 'test_name' al.build_inp() def setup_plot(self, pb): print('analysis: setup_plot') X_Id = self.xdomain.mesh.X_Id if len(self.hist.U_t) == 0: U_1 = np.zeros_like(X_Id) print('analysis: U_I', ) else: U_1 = self.hist.U_t[-1] U_1 = U_1.reshape(-1, self.xdomain.fets.n_nodal_dofs)[:, :3] X1_Id = X_Id + U_1 X1_Id = X1_Id.astype(np.float32) I_Ei = self.xdomain.I_Ei.astype(np.uint32) # Original state mesh wb_mesh_0 = k3d.mesh(self.xdomain.X_Id.astype(np.float32), I_Ei, color=0x999999, opacity=0.5, side='double') pb.plot_fig += wb_mesh_0 pb.objects['wb_mesh_0'] = wb_mesh_0 # Deformed state mesh wb_mesh_1 = k3d.mesh(X1_Id, I_Ei, color_map=k3d.colormaps.basic_color_maps.Jet, attribute=U_1[:, 2], color_range=[np.min(U_1), np.max(U_1)], side='double') pb.plot_fig += wb_mesh_1 pb.objects['wb_mesh_1'] = wb_mesh_1 if self.show_wireframe: k3d_mesh_wireframe = k3d.mesh(X1_Id, I_Ei, color=0x000000, wireframe=True) pb.plot_fig += k3d_mesh_wireframe pb.objects['mesh_wireframe'] = k3d_mesh_wireframe def update_plot(self, pb): X_Id = self.xdomain.mesh.X_Id print('analysis: update_plot') if len(self.hist.U_t) == 0: U_1 = np.zeros_like(X_Id) print('analysis: U_I', ) else: U_1 = self.hist.U_t[-1] U_1 = U_1.reshape(-1, self.xdomain.fets.n_nodal_dofs)[:, :3] X1_Id = X_Id + U_1 X1_Id = X1_Id.astype(np.float32) I_Ei = self.xdomain.I_Ei.astype(np.uint32) mesh = pb.objects['wb_mesh_1'] mesh.vertices = X1_Id mesh.indices = I_Ei mesh.attribute = U_1[:, 2] mesh.color_range = [np.min(U_1), np.max(U_1)] if self.show_wireframe: wireframe = pb.objects['mesh_wireframe'] wireframe.vertices = X1_Id wireframe.indices = I_Ei def get_Pw(self): import numpy as np F_to = self.hist.F_t U_to = self.hist.U_t _, _, loaded_dofs = self.bcs.bc_loaded F_loaded = np.sum(F_to[:, loaded_dofs], axis=-1) U_loaded = np.average(U_to[:, loaded_dofs], axis=-1) return U_loaded, F_loaded
class WBTessellation5PV2(WBNumTessellation): name = 'WBTessellation5PV2' wb_cell = bu.Instance(WBCell5ParamV2, ()) tree = ['wb_cell'] X_Ia = tr.DelegatesTo('wb_cell') I_Fi = tr.DelegatesTo('wb_cell') # sigma_sol_num = bu.Int(-1, GEO=True) # rho_sol_num = bu.Int(-1, GEO=True) sol_num = bu.Int(4, GEO=True) # Note: Update traits to 6.3.2 in order for the following command to work!! @tr.observe('wb_cell.+GEO', post_init=True) def update_after_wb_cell_GEO_changes(self, event): self.event_geo = not self.event_geo self.update_plot(self.pb) ipw_view = bu.View( *WBNumTessellation.ipw_view.content, # bu.Item('sigma_sol_num'), # bu.Item('rho_sol_num'), bu.Item('sol_num'), ) sol = tr.Property(depends_on='+GEO') @tr.cached_property def _get_sol(self): # rhos, sigmas = self.get_3_cells_angles() # print('original sigmas=', sigmas) # print('original rhos=', rhos) # # rhos = 2 * np.pi - rhos # # sigmas = 2 * np.pi - sigmas # rhos = -rhos # sigmas = -sigmas # print('sigmas=', sigmas) # print('rhos=', rhos) # # if self.sigma_sol_num != -1 and self.rho_sol_num != -1: # print('Solution {} was used.'.format(self.sigma_sol_num)) # sol = np.array([sigmas[self.sigma_sol_num - 1], rhos[self.rho_sol_num - 1]]) # return sol # # sigma_sol_idx = np.argmin(np.abs(sigmas - sol[0])) # rho_sol_idx = np.argmin(np.abs(rhos - sol[1])) # # if sigma_sol_idx != rho_sol_idx: # print('Warning: sigma_sol_idx != rho_sol_idx, num solution is picked!') # else: # diff = np.min(np.abs(sigmas - sol[0])) + np.min(np.abs(rhos - sol[1])) # print('Solution {} was picked (nearst to numerical sol), diff={}'.format(sigma_sol_idx + 1, diff)) # sol = np.array([sigmas[sigma_sol_idx], rhos[rho_sol_idx]]) sol_num = self.sol_num # Solving with only 4th solution rhos, sigmas = self.get_3_cells_angles(sol_num=sol_num) sol = np.array([sigmas[0], rhos[0]]) print('Ana. solution:', sol) return sol def get_3_cells_angles(self, sol_num=None): a = self.wb_cell.a b = self.wb_cell.b c = self.wb_cell.c gamma = self.wb_cell.gamma beta = self.wb_cell.beta cos_psi1 = ((b**2 - a**2) - a * sqrt(a**2 + b**2) * cos(beta)) / ( b * sqrt(a**2 + b**2) * sin(beta)) sin_psi1 = sqrt(a**2 * (3 * b**2 - a**2) + 2 * a * (b**2 - a**2) * sqrt(a**2 + b**2) * cos(beta) - (a**2 + b**2)**2 * cos(beta)**2) / ( b * sqrt(a**2 + b**2) * sin(beta)) cos_psi5 = (sqrt(a**2 + b**2) * cos(beta) - a * cos(2 * gamma)) / (b * sin(2 * gamma)) sin_psi5 = sqrt( b**2 + 2 * a * sqrt(a**2 + b**2) * cos(beta) * cos(2 * gamma) - (a**2 + b**2) * (cos(beta)**2 + cos(2 * gamma)**2)) / (b * sin(2 * gamma)) cos_psi6 = (a - sqrt(a**2 + b**2) * cos(beta) * cos(2 * gamma)) / ( sqrt(a**2 + b**2) * sin(beta) * sin(2 * gamma)) sin_psi6 = sqrt(b**2 + 2 * a * sqrt(a**2 + b**2) * cos(beta) * cos(2 * gamma) - (a**2 + b**2) * (cos(beta)**2 + cos(2 * gamma)**2)) / ( sqrt(a**2 + b**2) * sin(beta) * sin(2 * gamma)) cos_psi1plus6 = cos_psi1 * cos_psi6 - sin_psi1 * sin_psi6 sin_psi1plus6 = sin_psi1 * cos_psi6 + cos_psi1 * sin_psi6 sin_phi1 = sin_psi1plus6 sin_phi2 = sin_psi5 sin_phi3 = sin_psi5 sin_phi4 = sin_psi1plus6 cos_phi1 = cos_psi1plus6 cos_phi2 = cos_psi5 cos_phi3 = cos_psi5 cos_phi4 = cos_psi1plus6 # ALWAYS USE THIS TO FIND DEFINITE ANGLE IN [-pi, pi] from sin and cos phi1 = np.arctan2(sin_phi1, cos_phi1) phi2 = np.arctan2(sin_phi2, cos_phi2) phi3 = phi2 phi4 = phi1 e2 = (a - c)**2 + b**2 e = sqrt(e2) f = (a - c)**2 + b**2 * cos(phi1 + phi3) W = 2 * (1 - cos(phi1 + phi3)) * ( (a**2 - cos(phi2) * cos(phi4) * b**2) * c**2 * sin(2 * gamma)**2 - (cos(phi2) + cos(phi4)) * ((cos(2 * gamma) - 1) * c + 2 * a) * sin(2 * gamma) * a * b * c - 2 * a**2 * c * (2 * a - c) * (cos(2 * gamma) + 1) + 2 * a**2 * b**2 * (cos(phi1 + phi3) + 1)) - e2 * ( cos(phi2) - cos(phi4))**2 * c**2 * sin(2 * gamma)**2 T_rho = (cos(phi1 + phi3) - 1) * ( a * (b**2 * (cos(phi1 + phi3) + 1) + 2 * a * (a - c)) * (cos(2 * gamma) + 1) + b * ((e2 + f) * cos(phi2) + (a - c) * c * (cos(phi2) + cos(phi4))) * sin(2 * gamma) - 2 * a * b**2 * (cos(phi1 + phi3) + 1)) T_sigma = (cos(phi1 + phi3) - 1) * ( a * (b**2 * (cos(phi1 + phi3) + 1) + 2 * a * (a - c)) * (cos(2 * gamma) + 1) + b * ((e2 + f) * cos(phi4) + (a - c) * c * (cos(phi2) + cos(phi4))) * sin(2 * gamma) - 2 * a * b**2 * (cos(phi1 + phi3) + 1)) print('W:', W) print('T_rho:', T_rho) print('T_sigma:', T_sigma) rhos = [] sigmas = [] get_angle = lambda x: np.arctan(x) * 2 if sol_num == 1 or sol_num is None: sol_P1_t_1 = (b * (a - c) * (cos(phi1 + phi3) - 1) * sqrt(W) - b * (a * b * c * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * cos(2 * gamma) + (cos(phi2) * f - cos(phi4) * e2) * sin(phi1 + phi3) * c * sin(2 * gamma) + a * b * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * (2 * a - c)) + (cos(phi1 + phi3) - 1) * (e2 + f) * sqrt(4 * a * b**2 * (cos(phi2) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a) - (a**2 + b**2) * (cos(phi2) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a)**2) ) / (e * (b * sin(phi1 + phi3) * sqrt(W) - T_rho)) sol_Q1_u_1 = (b * (a - c) * (cos(phi1 + phi3) - 1) * sqrt(W) - b * (a * b * c * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * cos(2 * gamma) + (cos(phi4) * f - cos(phi2) * e2) * sin(phi1 + phi3) * c * sin(2 * gamma) + a * b * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * (2 * a - c)) + (cos(phi1 + phi3) - 1) * (e2 + f) * sqrt(4 * a * b**2 * (cos(phi4) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a) - (a**2 + b**2) * (cos(phi4) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a)**2) ) / (e * (b * sin(phi1 + phi3) * sqrt(W) - T_sigma)) rhos.append(get_angle(sol_P1_t_1)) sigmas.append(get_angle(sol_Q1_u_1)) print('sol_P1_t_1:', sol_P1_t_1) print('sol_Q1_u_1:', sol_Q1_u_1) if sol_num == 2 or sol_num is None: sol_P1_t_2 = (b * (a - c) * (cos(phi1 + phi3) - 1) * sqrt(W) - b * (a * b * c * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * cos(2 * gamma) + (cos(phi2) * f - cos(phi4) * e2) * sin(phi1 + phi3) * c * sin(2 * gamma) + a * b * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * (2 * a - c)) - (cos(phi1 + phi3) - 1) * (e2 + f) * sqrt(4 * a * b**2 * (cos(phi2) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a) - (a**2 + b**2) * (cos(phi2) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a)**2) ) / (e * (b * sin(phi1 + phi3) * sqrt(W) - T_rho)) sol_Q1_u_2 = (b * (a - c) * (cos(phi1 + phi3) - 1) * sqrt(W) - b * (a * b * c * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * cos(2 * gamma) + (cos(phi4) * f - cos(phi2) * e2) * sin(phi1 + phi3) * c * sin(2 * gamma) + a * b * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * (2 * a - c)) - (cos(phi1 + phi3) - 1) * (e2 + f) * sqrt(4 * a * b**2 * (cos(phi4) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a) - (a**2 + b**2) * (cos(phi4) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a)**2) ) / (e * (b * sin(phi1 + phi3) * sqrt(W) - T_sigma)) rhos.append(get_angle(sol_P1_t_2)) sigmas.append(get_angle(sol_Q1_u_2)) print('sol_P1_t_2:', sol_P1_t_2) print('sol_Q1_u_2:', sol_Q1_u_2) if sol_num == 3 or sol_num is None: sol_P2_t_1 = (-b * (a - c) * (cos(phi1 + phi3) - 1) * sqrt(W) - b * (a * b * c * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * cos(2 * gamma) + (cos(phi2) * f - cos(phi4) * e2) * sin(phi1 + phi3) * c * sin(2 * gamma) + a * b * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * (2 * a - c)) + (cos(phi1 + phi3) - 1) * (e2 + f) * sqrt(4 * a * b**2 * (cos(phi2) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a) - (a**2 + b**2) * (cos(phi2) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a)**2) ) / (e * (-b * sin(phi1 + phi3) * sqrt(W) - T_rho)) sol_Q2_u_1 = (-b * (a - c) * (cos(phi1 + phi3) - 1) * sqrt(W) - b * (a * b * c * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * cos(2 * gamma) + (cos(phi4) * f - cos(phi2) * e2) * sin(phi1 + phi3) * c * sin(2 * gamma) + a * b * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * (2 * a - c)) + (cos(phi1 + phi3) - 1) * (e2 + f) * sqrt(4 * a * b**2 * (cos(phi4) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a) - (a**2 + b**2) * (cos(phi4) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a)**2) ) / (e * (-b * sin(phi1 + phi3) * sqrt(W) - T_sigma)) rhos.append(get_angle(sol_P2_t_1)) sigmas.append(get_angle(sol_Q2_u_1)) print('sol_P2_t_1:', sol_P2_t_1) print('sol_Q2_u_1:', sol_Q2_u_1) if sol_num == 4 or sol_num is None: sol_P2_t_2 = (-b * (a - c) * (cos(phi1 + phi3) - 1) * sqrt(W) - b * (a * b * c * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * cos(2 * gamma) + (cos(phi2) * f - cos(phi4) * e2) * sin(phi1 + phi3) * c * sin(2 * gamma) + a * b * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * (2 * a - c)) - (cos(phi1 + phi3) - 1) * (e2 + f) * sqrt(4 * a * b**2 * (cos(phi2) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a) - (a**2 + b**2) * (cos(phi2) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a)**2) ) / (e * (-b * sin(phi1 + phi3) * sqrt(W) - T_rho)) sol_Q2_u_2 = (-b * (a - c) * (cos(phi1 + phi3) - 1) * sqrt(W) - b * (a * b * c * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * cos(2 * gamma) + (cos(phi4) * f - cos(phi2) * e2) * sin(phi1 + phi3) * c * sin(2 * gamma) + a * b * sin(phi1 + phi3) * (cos(phi1 + phi3) - 1) * (2 * a - c)) - (cos(phi1 + phi3) - 1) * (e2 + f) * sqrt(4 * a * b**2 * (cos(phi4) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a) - (a**2 + b**2) * (cos(phi4) * sin(2 * gamma) * b + (cos(2 * gamma) + 1) * a)**2) ) / (e * (-b * sin(phi1 + phi3) * sqrt(W) - T_sigma)) print('sol_P2_t_2:', sol_P2_t_2) print('sol_Q2_u_2:', sol_Q2_u_2) rhos.append(get_angle(sol_P2_t_2)) sigmas.append(get_angle(sol_Q2_u_2)) # sol_P is tan(rho/2), sol_Q is tan(sigma/2) return -np.array(rhos), -np.array(sigmas)
class MATS3DSlideStrain(MATS3DEval): r''' Isotropic damage model. ''' node_name = 'Slide3D' n_a = tr.Array(np.float_, value=[0, 1, 0]) slide_displ = bu.Instance(Slide34, ()) slide_displ_ = tr.Property(depends_on='+MAT') '''Reconfigure slide with the derived stiffness parameters''' @tr.cached_property def _get_slide_displ_(self): self.slide_displ.trait_set(E_N=self.E, E_T=self.E_T) return self.slide_displ tree = ['slide_displ'] ipw_view = bu.View(bu.Item('n_a'), bu.Item('slide_displ')) state_var_shapes = tr.Property @tr.cached_property def _get_state_var_shapes(self): return self.slide_displ_.state_var_shapes r''' Shapes of the state variables to be stored in the global array at the level of the domain. ''' def get_eps_N(self, eps_ij, n_i): eps_N = np.einsum('...ij,...i,...j->...', eps_ij, n_i, n_i) return eps_N def get_eps_T(self, eps_ij, n_i): delta_ij = np.identity(3) eps_T = 0.5 * ( np.einsum('...i,...jk,...ij->...k', n_i, delta_ij, eps_ij) + np.einsum('...j,...ik,...ij->...k', n_i, delta_ij, eps_ij) - 2 * np.einsum('...i,...j,...k,...ij->...k', n_i, n_i, n_i, eps_ij)) return eps_T def get_eps_T_p(self, eps_T_p, eps_T): director_vector = [0, 0, 1] eps_T_p = np.einsum('...,...i->...i', eps_T_p, director_vector) return eps_T_p E_T = tr.Property(depends_on='MAT') @tr.cached_property def _get_E_T(self): n_i = self.n_a delta_ij = np.identity(3) D_ijkl = self.D_abef operator = 0.5 * (np.einsum('i,jk,l->ijkl', n_i, delta_ij, n_i) + np.einsum('j,ik,l->jikl', n_i, delta_ij, n_i) - 2 * np.einsum('i,j,k,l->ijkl', n_i, n_i, n_i, n_i)) E_T = np.einsum('ijkl,ijkl->', D_ijkl, operator) return E_T def get_corr_pred(self, eps_Emab_n1, tn1, **state): r''' Corrector predictor computation. ''' n_i = self.n_a eps_ij = eps_Emab_n1 eps_N = np.einsum('...ij,...i,...j->...', eps_ij, n_i, n_i) eps_T = self.get_eps_T(eps_ij, n_i) eps_T = np.sqrt(np.einsum('...i,...i->...', eps_T, eps_T)) eps_NT_Ema = np.concatenate( [np.transpose(eps_N), np.transpose(eps_T)], axis=-1) print('eps_NT_Ema', eps_NT_Ema.shape) print(self.state_var_shapes) se = self.slide_displ_ sig_NT_Ema, D_Emab = se.get_corr_pred(eps_NT_Ema, tn1, **state) eps_N_p, eps_T_p_x, eps_T_p_y = state['w_pi'], state['s_pi_x'], state[ 's_pi_y'] eps_T = self.get_eps_T(eps_ij, n_i) eps_T_p_i = self.get_eps_T_p(eps_T_p_x, eps_T) omega_N_Em, omega_T_Em = state['omega_N'], state['omega_T'] print(eps_ij.shape) phi_Emab = np.zeros_like(eps_ij) phi_Emab[:, 1, 1] = 0. phi_Emab[:, 2, 2] = np.sqrt(1 - omega_T_Em) phi_Emab[:, 0, 0] = np.sqrt(1 - omega_N_Em) beta_Emijkl = np.einsum('...ik,...jl->...ijkl', phi_Emab, np.transpose(phi_Emab, (1, 0))) eps_ij_p = (np.einsum('i,...j->...ij', n_i, eps_T_p_i) + np.einsum('...i,j->...ij', eps_T_p_i,n_i)) + \ np.einsum('...,i,j->...ij',eps_N_p, n_i, n_i) D_abef = self.D_abef D_Emabcd = np.einsum('...ijkl,klrs,...rstu->...ijtu', beta_Emijkl, D_abef, beta_Emijkl) sigma_Emab = np.einsum('...ijkl,...kl->...ij', D_Emabcd, (eps_Emab_n1 - eps_ij_p)) return sigma_Emab, D_Emabcd
class PullOutModel(TStepBC, BMCSRootNode, Vis2D): name = 'Pullout' node_name = 'Pull out simulation' hist_type = PulloutHist2 history = tr.Property() @tr.cached_property def _get_history(self): return self.hist time_line = tr.Property() @tr.cached_property def _get_time_line(self): return self.sim.tline tree = [ 'time_line', 'cross_section', 'geometry', 'material_model', 'loading_scenario', 'history' ] def run(self): self.sim.run() def reset(self): self.sim.reset() t = tr.Property() def _get_t(self): return self.sim.t def _set_t(self, value): self.sim.t = value t_max = tr.Property() def _get_t_max(self): return self.sim.t_max def _set_t_max(self, value): self.sim.t_max = value interrupt = tr.Property() def _get_interrupt(self): return self.sim.interrupt def _set_interrupt(self, value): self.sim.interrupt = value ipw_view = bu.View(bu.Item('w_max'), bu.Item('n_e_x'), bu.Item('fixed_boundary'), bu.Item('material_model'), bu.Item('loading_scenario'), time_editor=bu.ProgressEditor( run_method='run', reset_method='reset', interrupt_var='interrupt', time_var='t', time_max='t_max', )) @tr.on_trait_change("state_changed") def report_change(self): self.model_structure_changed = True # ========================================================================= # Test setup parameters # ========================================================================= loading_scenario = bu.EitherType( options=[('monotonic', MonotonicLoadingScenario), ('cyclic', CyclicLoadingScenario)], report=True, TIME=True, desc='object defining the loading scenario') cross_section = bu.Instance(CrossSection, report=True, desc='cross section parameters') def _cross_section_default(self): return CrossSection() geometry = bu.Instance( Geometry, report=True, desc='geometry parameters of the boundary value problem') def _geometry_default(self): return Geometry() control_variable = bu.Enum(options=['u', 'f'], auto_set=False, enter_set=True, desc=r'displacement or force control: [u|f]', BC=True) # ========================================================================= # Discretization # ========================================================================= n_e_x = bu.Int(20, MESH=True, auto_set=False, enter_set=True, symbol='n_\mathrm{E}', unit='-', desc='number of finite elements along the embedded length') # ========================================================================= # Algorithimc parameters # ========================================================================= k_max = Int(400, unit='-', symbol='k_{\max}', desc='maximum number of iterations', ALG=True) tolerance = Float(1e-4, unit='-', symbol='\epsilon', desc='required accuracy', ALG=True) material_model = bu.EitherType( options=[ ('multilinear', MATS1D5BondSlipMultiLinear), ('trilinear', MATS1D5BondSlipTriLinear), ('damage', MATS1D5BondSlipD), ('elasto-plasticity', MATS1D5BondSlipEP), # ('cumulative fatigue', MATSBondSlipFatigue) ], MAT=True) '''Material model''' mm = Property def _get_mm(self): return self.material_model_ material = Property def _get_material(self): return self.material_model_ # ========================================================================= # Finite element type # ========================================================================= fets_eval = Property(bu.Instance(FETS1D52ULRH), depends_on="state_changed") '''Finite element time stepper implementing the corrector predictor operators at the element level''' @cached_property def _get_fets_eval(self): return FETS1D52ULRH(A_m=self.cross_section.A_m, P_b=self.cross_section.P_b, A_f=self.cross_section.A_f) dots_grid = Property(bu.Instance(XDomainFEInterface1D), depends_on="state_changed") '''Discretization object. ''' @cached_property def _get_dots_grid(self): geo = self.geometry return XDomainFEInterface1D(dim_u=2, coord_max=[geo.L_x], shape=[self.n_e_x], fets=self.fets_eval) fe_grid = Property def _get_fe_grid(self): return self.dots_grid.mesh domains = Property(depends_on='state_changed') @cached_property def _get_domains(self): return [(self.dots_grid, self.material_model_)] # ========================================================================= # Boundary conditions # ========================================================================= w_max = bu.Float(1, BC=True, symbol='w_{\max}', unit='mm', desc='maximum pullout slip', auto_set=False, enter_set=True) u_f0_max = Property(depends_on='BC') @cached_property def _get_u_f0_max(self): return self.w_max def _set_u_f0_max(self, value): self.w_max = value fixed_boundary = bu.Enum( options=[ 'non-loaded end (matrix)', 'loaded end (matrix)', 'non-loaded end (reinf)', 'clamped left' ], BC=True, desc= 'which side of the specimen is fixed [non-loaded end [matrix], loaded end [matrix], non-loaded end [reinf]]' ) fixed_dofs = Property(depends_on="state_changed") @cached_property def _get_fixed_dofs(self): if self.fixed_boundary == 'non-loaded end (matrix)': return [0] elif self.fixed_boundary == 'non-loaded end (reinf)': return [1] elif self.fixed_boundary == 'loaded end (matrix)': return [self.controlled_dof - 1] elif self.fixed_boundary == 'clamped left': return [0, 1] controlled_dof = Property(depends_on="state_changed") @cached_property def _get_controlled_dof(self): return 2 + 2 * self.n_e_x - 1 free_end_dof = Property(depends_on="state_changed") @cached_property def _get_free_end_dof(self): return 1 fixed_bc_list = Property(depends_on="state_changed") '''Foxed boundary condition''' @cached_property def _get_fixed_bc_list(self): return [ BCDof(node_name='fixed left end', var='u', dof=dof, value=0.0) for dof in self.fixed_dofs ] control_bc = Property(depends_on="state_changed") '''Control boundary condition - make it accessible directly for the visualization adapter as property ''' @cached_property def _get_control_bc(self): return BCDof(node_name='pull-out displacement', var=self.control_variable, dof=self.controlled_dof, value=self.w_max, time_function=self.loading_scenario_) bc = Property(depends_on="state_changed") @cached_property def _get_bc(self): return [self.control_bc] + self.fixed_bc_list X_M = Property() def _get_X_M(self): state = self.fe_domain[0] return state.xmodel.x_Ema[..., 0].flatten() # ========================================================================= # Getter functions @todo move to the PulloutStateRecord # ========================================================================= P = tr.Property def _get_P(self): c_dof = self.controlled_dof return self.F_k[c_dof] w_L = tr.Property def _get_w_L(self): c_dof = self.controlled_dof return self.U_n[c_dof] w_0 = tr.Property def _get_w_0(self): f_dof = self.free_end_dof return self.U_n[f_dof] def get_shear_integ(self): sf_t_Em = np.array(self.tloop.sf_Em_record) w_ip = self.fets_eval.ip_weights J_det = self.tstepper.J_det P_b = self.cross_section.P_b shear_integ = np.einsum('tEm,m,em->t', sf_t_Em, w_ip, J_det) * P_b return shear_integ def plot_omega(self, ax, vot): X_J = self.X_J omega = self.get_omega(vot) ax.fill_between(X_J, 0, omega, facecolor='lightcoral', alpha=0.3) ax.plot(X_J, omega, linewidth=2, color='lightcoral', label='bond') ax.set_ylabel('damage') ax.set_xlabel('bond length') ax.legend(loc=2) return 0.0, 1.05 def plot_eps_s(self, ax, vot): eps_p = self.get_eps_p(vot).T s = self.get_s(vot) ax.plot(eps_p[1], s, linewidth=2, color='lightcoral') ax.set_ylabel('reinforcement strain') ax.set_xlabel('slip') def subplots(self, fig): (ax_geo, ax_Pw), (ax_energy, ax_dG_t) = fig.subplots(2, 2) return ax_geo, ax_Pw, ax_energy, ax_dG_t def update_plot(self, axes): if len(self.history.U_t) == 0: return ax_geo, ax_Pw, ax_energy, ax_dG_t = axes self.history.t_slider = self.t self.history.plot_geo(ax_geo) self.history.plot_Pw(ax_Pw) self.history.plot_G_t(ax_energy) self.history.plot_dG_t(ax_dG_t)
class WBShellFETriangularMesh(FETriangularMesh): """Directly mapped mesh with one-to-one mapping """ name = 'WBShellFETriangularMesh' plot_backend = 'k3d' geo = bu.Instance(WBShellGeometry4P) I_CDij = tr.DelegatesTo('geo') unique_node_map = tr.DelegatesTo('geo') n_phi_plus = tr.DelegatesTo('geo') direct_mesh = bu.Bool(False, DSC=True) subdivision = bu.Float(3, DSC=True) # Will be used in the parent class. Should be here to catch GEO dependency show_wireframe = bu.Bool(True, GEO=True) ipw_view = bu.View( *FETriangularMesh.ipw_view.content, bu.Item('subdivision'), bu.Item('direct_mesh'), bu.Item('export_vtk'), bu.Item('show_wireframe'), ) mesh = tr.Property(depends_on='state_changed') @tr.cached_property def _get_mesh(self): X_Id = self.geo.X_Ia I_Fi = self.geo.I_Fi mesh_size = np.linalg.norm(X_Id[1] - X_Id[0]) / self.subdivision with pygmsh.geo.Geometry() as geom: xpoints = np.array( [geom.add_point(X_d, mesh_size=mesh_size) for X_d in X_Id]) for I_i in I_Fi: # Create points. Facet(geom, xpoints[I_i]) # geom.add_polygon(X_id, mesh_size=mesh_size) gmsh.model.geo.remove_all_duplicates() mesh = geom.generate_mesh() return mesh X_Id = tr.Property def _get_X_Id(self): if self.direct_mesh: return self.geo.X_Ia return np.array(self.mesh.points, dtype=np.float_) I_Fi = tr.Property def _get_I_Fi(self): if self.direct_mesh: return self.geo.I_Fi return self.mesh.cells[1][1] bc_fixed_nodes = tr.Array(np.int_, value=[]) bc_loaded_nodes = tr.Array(np.int_, value=[]) export_vtk = bu.Button @tr.observe('export_vtk') def write(self, event=None): self.mesh.write("test_shell_mesh.vtk") def setup_plot(self, pb): super(WBShellFETriangularMesh, self).setup_plot(pb) X_Id = self.X_Id.astype(np.float32) fixed_nodes = self.bc_fixed_nodes loaded_nodes = self.bc_loaded_nodes X_Ma = X_Id[fixed_nodes] k3d_fixed_nodes = k3d.points(X_Ma, color=0x22ffff, point_size=100) pb.plot_fig += k3d_fixed_nodes pb.objects['fixed_nodes'] = k3d_fixed_nodes X_Ma = X_Id[loaded_nodes] k3d_loaded_nodes = k3d.points(X_Ma, color=0xff22ff, point_size=100) pb.plot_fig += k3d_loaded_nodes pb.objects['loaded_nodes'] = k3d_loaded_nodes def update_plot(self, pb): super(WBShellFETriangularMesh, self).update_plot(pb) fixed_nodes = self.bc_fixed_nodes loaded_nodes = self.bc_loaded_nodes X_Id = self.X_Id.astype(np.float32) pb.objects['fixed_nodes'].positions = X_Id[fixed_nodes] pb.objects['loaded_nodes'].positions = X_Id[loaded_nodes]