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 Conjoint(Model): # The imput data for calculation owner_ref = _traits.WeakRef() # design = DataSet() design = _traits.DelegatesTo('owner_ref') design_vars = _traits.List(_traits.Str()) liking = DataSet() # consumers = DataSet() consumers = _traits.DelegatesTo('owner_ref') consumers_vars = _traits.List(_traits.Str()) # Conjoint settings model_struct = _traits.Enum('Struct 1', 'Struct 2', 'Struct 3') # Conjoint calculation state ccs = _traits.Instance(ConjointCalcState, ()) cm = _traits.Instance(ConjointMachine) # depends_on res = _traits.Property( depends_on='design_vars, consumers_vars, model_struct') def _cm_default(self): try: return ConjointMachine() except RNotFoundException: self.ccs.messages = ("Was not able to find and start R.\n" "You have to check the installation of R") self.ccs.edit_traits(kind='livemodal') @_traits.on_trait_change('owner_ref.model_struct') def _struc_altered(self, new): self.model_struct = new @_traits.on_trait_change('owner_ref.sel_design_var') def _des_var_altered(self, new): self.design_vars = new @_traits.on_trait_change('owner_ref.sel_cons_char') def _cons_char_altered(self, new): self.consumers_vars = new @_traits.cached_property def _get_res(self): if not self.cm.run_state: self.cm.run_state = self.ccs model = { 'Struct 1': 1, 'Struct 2': 2, 'Struct 3': 3 }[self.model_struct] self.cm.schedule_calculation(self.design, sorted(self.design_vars), self.liking, model, self.consumers, sorted(self.consumers_vars)) self.ccs.edit_traits(kind='livemodal') return self.cm.get_result()
class Hist(tr.HasStrictTraits): model = tr.Instance(IModel) tstep = tr.DelegatesTo('model') def init_state(self): pass
class Simulator(BMCSTreeNode, TLineMixIn): r'''Base class for simulators included in the BMCS Tool Suite. It implements the state dependencies within the simulation tree. It handles also the communication between the simulation and the user interface in several modes of interaction. ''' name = 'simulator' @tr.observe('state_changed') def _model_structure_changed(self, event=None): self.tloop.restart = True #========================================================================= # TIME LOOP #========================================================================= tloop = Property(Instance(ITLoop), depends_on='state_changed') r'''Time loop constructed based on the current model. ''' @cached_property def _get_tloop(self): return self.tstep.tloop_type(tstep=self.tstep, tline=self.tline) def __init__(self, tstep, *args, **kw): self.tstep = tstep super(Simulator, self).__init__(*args, **kw) tstep = tr.WeakRef(ITStep) hist = tr.Property def _get_hist(self): return self.tstep.hist def run(self): r'''Run a thread if it does not exist - do nothing otherwise ''' self.tloop() return interrupt = tr.DelegatesTo('tloop') def reset(self): self.tloop.reset() ipw_view = bu.View( run_method='tloop', reset_method='reset', interrupt_var='interrupt', time_var='t', time_max='t_max', )
class BMCSStudy(ReportStudy): '''Combine the simulater with specification of outputs ''' model = tr.Instance(IModel) sim = tr.Property(depends_on='model') def _get_sim(self): return self.model.sim '''Model of the studied phoenomenon. ''' viz_sheet = tr.Instance(BMCSVizSheet, ()) '''Sheet for 2d visualization. ''' input = tr.Property def _get_input(self): return self.sim output = tr.Property def _get_output(self): return self.viz_sheet offline = tr.DelegatesTo('viz_sheet') n_cols = tr.DelegatesTo('viz_sheet') def _sim_changed(self): self.sim.set_ui_recursively(self) tline = self.sim.tline self.viz_sheet.time_range_changed(tline.max) self.viz_sheet.time_changed(tline.val) running = tr.Bool(False) enable_run = tr.Bool(True) enable_pause = tr.Bool(False) enable_stop = tr.Bool(False) def _running_changed(self): '''If the simulation is running disable the run botton, enable the pause button and disable changes in all input parameters. ''' self.enable_run = not self.running self.enable_pause = self.running self.sim.set_traits_with_metadata(self.enable_run, disable_on_run=True) start_event = tr.Event '''Event announcing the start of the calculation ''' def _start_event_fired(self): self.viz_sheet.run_started() finish_event = tr.Event '''Event announcing the start of the calculation ''' def _finish_event_fired(self): self.viz_sheet.run_finished() def run(self): self.sim.run_thread() self.enable_stop = True def join(self): self.sim.join_thread() def pause(self): self.sim.pause() def stop(self): self.sim.stop() self.enable_stop = False def report_tex(self): r = Reporter(report_name=self.sim.name, input=self.sim, output=self.viz_sheet) r.write() r.show_tex() def report_pdf(self): r = Reporter(studies=[self]) r.write() r.show_tex() r.run_pdflatex() r.show_pdf() def add_viz2d(self, clname, name, **kw): self.sim.add_viz2d(clname, name, **kw)
class Vis2DField(Vis2D): model = tr.DelegatesTo('sim') x_file = tr.File file_list = tr.List(tr.File) var = tr.Str('<unnamed>') dir = tr.Directory def new_dir(self): self.dir = tempfile.mkdtemp() def setup(self): self.new_dir() # make a loop over the DomainState fe_domain = self.sim.tstep.fe_domain domain = fe_domain[2] xdomain = domain.xdomain r_Eia = np.einsum( 'Eira,Eia->Eir', xdomain.T_Emra[..., :xdomain.x_Eia.shape[-1]], xdomain.x_Eia ) file_name = 'slice_x_%s' % (self.var,) target_file = os.path.join( self.dir, file_name.replace('.', '_') + '.npy' ) #print('r', r_Eia[..., :-1]) np.save(target_file, r_Eia[..., :-1]) self.x_file = target_file def get_x_Eir(self): return np.load(self.x_file) def update(self): ts = self.sim.tstep fe_domain = self.sim.tstep.fe_domain domain = fe_domain[2] xdomain = domain.xdomain U = ts.U_k t = ts.t_n1 s_Emr = xdomain.map_U_to_field(U) var_function = domain.tmodel.var_dict.get(self.var, None) if var_function == None: raise ValueError('no such variable' % self.var) state_k = copy.deepcopy(domain.state_n) var_k = var_function(s_Emr, ts.t_n1, **state_k) target_file = self.filename(t) #np.save(target_file, s_Emr) np.save(target_file, var_k) self.file_list.append(target_file) def filename(self, t): file_name = 'slice_%s_step_%008.4f' % (self.var, t) target_file = os.path.join( self.dir, file_name.replace('.', '_') ) + '.npy' return target_file
class TriXDomainFE(XDomainFE): name = 'TriXDomainFE' ''' Finite element discretization with dofs and mappings derived from the FE definition ''' mesh = tr.Instance(FETriangularMesh, ()) fets = tr.DelegatesTo('mesh') tree = ['mesh'] change = tr.Event(GEO=True) plot_backend = 'k3d' n_dofs = tr.Property def _get_n_dofs(self): return len(self.mesh.X_Id) * self.mesh.n_nodal_dofs eta_w = tr.Property r'''Weight factors for numerical integration. ''' def _get_eta_w(self): return self.fets.w_m Na_deta = tr.Property() r'''Derivatives of the shape functions in the integration points. ''' @tr.cached_property def _get_Na_deta(self): return np.einsum('imr->mri', self.fets.dN_imr) x_0 = tr.Property() r'''Derivatives of the shape functions in the integration points. ''' @tr.cached_property def _get_x_0(self): return self.mesh.X_Id x = tr.Property() r'''Derivatives of the shape functions in the integration points. ''' @tr.cached_property def _get_x(self): return self.x_0 F = tr.Property() r'''Derivatives of the shape functions in the integration points. ''' @tr.cached_property def _get_F(self): return self.mesh.I_Fi T_Fab = tr.Property(depends_on='+GEO') @tr.cached_property def _get_T_Fab(self): return self.F_L_bases[:, 0, :] I_Ei = tr.Property def _get_I_Ei(self): return self.F_N x_Eia = tr.Property(depends_on='+GEO') @tr.cached_property def _get_x_Eia(self): X_Eia = self.X_Id[self.I_Ei, :] X_E0a = X_Eia[:, 0, :] X_Eia -= X_E0a[:, np.newaxis, :] # X_Eic = np.einsum('Eac,Eic->Eia', self.T_Fab, X_Eia) # return X_Eic[...,:-1] return X_Eia[..., :-1] def U2u(self, U_Eia): u0_Eia = U_Eia[..., :-1] return u0_Eia def xU2u(self, U_Eia): # u1_Eia = np.einsum('Eab,Eib->Eia', self.T_Fab, U_Eia) # u2_Eie = np.einsum('ea,Eia->Eie', DELTA23_ab, u1_Eia) u2_Eie = np.einsum('ea,Eia->Eie', DELTA23_ab, U_Eia) return u2_Eie def f2F(self, f_Eid): F0_Eia = np.concatenate([f_Eid, np.zeros_like(f_Eid[..., :1])], axis=-1) return F0_Eia def xf2F(self, f_Eid): F1_Eia = np.einsum('da,Eid->Eia', DELTA23_ab, f_Eid) # F2_Eia = np.einsum('Eab,Eia->Eib', self.T_Fab, F1_Eia) # return F2_Eia return F1_Eia def k2K(self, K_Eiejf): K0_Eicjf = np.concatenate( [K_Eiejf, np.zeros_like(K_Eiejf[:, :, :1, :, :])], axis=2) K0_Eicjd = np.concatenate( [K0_Eicjf, np.zeros_like(K0_Eicjf[:, :, :, :, :1])], axis=4) return K0_Eicjd def xk2K(self, K_Eiejf): K1_Eiejf = np.einsum('ea,fb,Eiejf->Eiajb', DELTA23_ab, DELTA23_ab, K_Eiejf) # correct # T_Eeafb = np.einsum('Eea,Efb->Eeafb', self.T_Fab, self.T_Fab) #K_Eab = np.einsum('Eeafb,ef->Eab', T_Eeafb, k_ef) # K2_Eiajb = np.einsum('Eeafb,Eiejf->Eiajb', T_Eeafb, K1_Eiejf) #K2_Eicjd = np.einsum('Eca,Ebd,Eiajb->Eicjd', self.T_Fab, self.T_Fab, K1_Eicjd) #K2_Eicjd = np.einsum('Eac,Edb,Eiajb->Eicjd', self.T_Fab, self.T_Fab, K1_Eicjd) return K1_Eiejf I_CDij = tr.Property def _get_I_CDij(self): return self.mesh.I_CDij bc_J_F_xyz = tr.Property(depends_on='state_changed') @tr.cached_property def _get_bc_J_F_xyz(self): ix2 = int((self.mesh.n_phi_plus) / 2) F_I = self.I_CDij[ix2, :, 0, :].flatten() _, idx_remap = self.mesh.unique_node_map return idx_remap[F_I] bc_J_xyz = tr.Property(depends_on='state_changed') @tr.cached_property def _get_bc_J_xyz(self): I_M = self.I_CDij[(0, -1), :, (0, -1), :].flatten() _, idx_remap = self.mesh.unique_node_map J_M = idx_remap[I_M] return J_M bc_J_x = tr.Property(depends_on='state_changed') @tr.cached_property def _get_bc_J_x(self): I_M = self.I_CDij[:, (0, -1), :, (0, -1)].flatten() _, idx_remap = self.mesh.unique_node_map J_M = idx_remap[I_M] return J_M def setup_plot(self, pb): X_Ia = self.mesh.X_Ia.astype(np.float32) I_Fi = self.mesh.I_Fi.astype(np.uint32) X_Ma = X_Ia[self.bc_J_xyz] self.k3d_fixed_xyz = k3d.points(X_Ma) pb.plot_fig += self.k3d_fixed_xyz X_Ma = X_Ia[self.bc_J_x] self.k3d_fixed_x = k3d.points(X_Ma, color=0x22ffff) pb.plot_fig += self.k3d_fixed_x X_Ma = X_Ia[self.bc_J_F_xyz] self.k3d_load_z = k3d.points(X_Ma, color=0xff22ff) pb.plot_fig += self.k3d_load_z self.k3d_mesh = k3d.mesh(X_Ia, I_Fi, color=0x999999, side='double') pb.plot_fig += self.k3d_mesh def update_plot(self, pb): X_Ia = self.mesh.X_Ia.astype(np.float32) I_Fi = self.mesh.I_Fi.astype(np.uint32) self.k3d_fixed_xyz.positions = X_Ia[self.bc_J_xyz] self.k3d_fixed_x.positions = X_Ia[self.bc_J_x] self.k3d_load_z.positions = X_Ia[self.bc_J_F_xyz] mesh = self.k3d_mesh mesh.vertices = X_Ia mesh.indices = I_Fi B_Eso = tr.Property(depends_on='+GEO') @tr.cached_property def _get_B_Eso(self): xx_Ei, yy_Ei = np.einsum('...a->a...', self.X_Id[self.I_Ei, :-1]) # xx_Ei, yy_Ei = np.einsum('...a->a...', self.mesh.X_Id[self.mesh.I_Fi, :-1]) y23 = yy_Ei[:, 1] - yy_Ei[:, 2] y31 = yy_Ei[:, 2] - yy_Ei[:, 0] y12 = yy_Ei[:, 0] - yy_Ei[:, 1] x32 = xx_Ei[:, 2] - xx_Ei[:, 1] x13 = xx_Ei[:, 0] - xx_Ei[:, 2] x21 = xx_Ei[:, 1] - xx_Ei[:, 0] x23 = -x32 y32 = -y23 y13 = -y31 J_Ear = np.array([[x13, y13], [x23, y23]]) J_Ear = np.einsum('ar...->...ar', J_Ear) det_J_E = np.linalg.det(J_Ear) O = np.zeros_like(y23) B_soE = np.array([[y23, O, y31, O, y12, O], [O, x32, O, x13, O, x21], [x32, y23, x13, y31, x21, y12]]) B_Eso = np.einsum('soE,E->Eso', B_soE, 1 / det_J_E) return B_Eso, det_J_E def map_U_to_field(self, U_o): print('') print('U_o,', U_o) U_Eia = U_o[self.o_Eia] # coordinate transform to local u_Eia = self.xU2u(U_Eia) u_Eo = u_Eia.reshape(-1, 6) B_Eso, _ = self.B_Eso eps_Eso = np.einsum('Eso,Eo->Es', B_Eso, u_Eo) print('eps_Eso,', eps_Eso) return eps_Eso def map_field_to_F(self, sig_Es): # print('map_field_to_F:') # print('sig_Es:', sig_Es) B_Eso, det_J_E = self.B_Eso f_Eo = self.integ_factor * np.einsum('Eso,Es,E->Eo', B_Eso, sig_Es, det_J_E / 2) f_Eic = f_Eo.reshape(-1, 3, 2) # coordinate transform to global f_Eic = self.xf2F(f_Eic) _, n_i, n_c = f_Eic.shape f_Ei = f_Eic.reshape(-1, n_i * n_c) o_E = self.o_Eia.reshape(-1, n_i * n_c) return o_E.flatten(), f_Ei.flatten() def map_field_to_K(self, D_Est): # print('map_field_to_K:') #========================================================================== B_Eso, det_J_E = self.B_Eso k2_ij = self.integ_factor * np.einsum('Eso,Est,Etp,E->Eop', B_Eso, D_Est, B_Eso, det_J_E / 2) K_Eiejf = k2_ij.reshape(-1, 3, 2, 3, 2) K_Eicjd = self.xk2K(K_Eiejf) _, n_i, n_c, n_j, n_d = K_Eicjd.shape K_Eij = K_Eicjd.reshape(-1, n_i * n_c, n_j * n_d) o_Ei = self.o_Eia.reshape(-1, n_i * n_c) # print('K_Eij:', K_Eij) print('o_Ei:', o_Ei) return SysMtxArray(mtx_arr=K_Eij, dof_map_arr=o_Ei) # ========================================================================= # Property operators for initial configuration # ========================================================================= F0_normals = tr.Property(tr.Array, depends_on='X, L, F') r'''Normal facet vectors. ''' @tr.cached_property def _get_F0_normals(self): x_F = self.x_0[self.F] N_deta_ip = self.Na_deta r_deta = np.einsum('ajK,IKi->Iaij', N_deta_ip, x_F) Fa_normals = np.einsum('Iai,Iaj,ijk->Iak', r_deta[..., 0], r_deta[..., 1], EPS) return np.sum(Fa_normals, axis=1) sign_normals = tr.Property(tr.Array, depends_on='X,L,F') r'''Orientation of the normal in the initial state. This array is used to switch the normal vectors of the faces to be oriented in the positive sense of the z-axis. ''' @tr.cached_property def _get_sign_normals(self): return np.sign(self.F0_normals[:, 2]) F_N = tr.Property(tr.Array, depends_on='X,L,F') r'''Counter-clockwise enumeration. ''' @tr.cached_property def _get_F_N(self): turn_facets = np.where(self.sign_normals < 0) F_N = np.copy(self.F) F_N[turn_facets, :] = self.F[turn_facets, ::-1] return F_N F_normals = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normals of the facets. ''' @tr.cached_property def _get_F_normals(self): n = self.Fa_normals return np.sum(n, axis=1) F_normals_0 = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normals of the facets. ''' @tr.cached_property def _get_F_normals_0(self): n = self.Fa_normals_0 return np.sum(n, axis=1) norm_F_normals = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normed normals of the facets. ''' @tr.cached_property def _get_norm_F_normals(self): n = self.F_normals mag_n = np.sqrt(np.einsum('...i,...i', n, n)) return n / mag_n[:, np.newaxis] norm_F_normals_0 = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normed normals of the facets. ''' @tr.cached_property def _get_norm_F_normals_0(self): n = self.F_normals_0 mag_n = np.sqrt(np.einsum('...i,...i', n, n)) return n / mag_n[:, np.newaxis]
class SimControler(BMCSTreeNode, TLineMixIn): model = tr.Instance(IModel) hist = tr.DelegatesTo('model') tstep = tr.DelegatesTo('model') tloop = tr.DelegatesTo('model')
class EnsembleTrainer(t.HasStrictTraits): def __init__(self, config={}, **kwargs): trainer_template = Trainer(**config) super().__init__(trainer_template=trainer_template, config=config, **kwargs) config: dict = t.Dict() trainer_template: Trainer = t.Instance(Trainer) trainers: ty.List[Trainer] = t.List(t.Instance(Trainer)) n_folds = t.Int(5) dl_test: DataLoader = t.DelegatesTo("trainer_template") data_spec: dict = t.DelegatesTo("trainer_template") cuda: bool = t.DelegatesTo("trainer_template") device: str = t.DelegatesTo("trainer_template") loss_func: str = t.DelegatesTo("trainer_template") batch_size: int = t.DelegatesTo("trainer_template") win_len: int = t.DelegatesTo("trainer_template") has_null_class: bool = t.DelegatesTo("trainer_template") predict_null_class: bool = t.DelegatesTo("trainer_template") name: str = t.Str() def _name_default(self): import time modelstr = "Ensemble" timestr = time.strftime("%Y%m%d-%H%M%S") return f"{modelstr}_{timestr}" X_folds = t.Tuple(transient=True) ys_folds = t.Tuple(transient=True) def _trainers_default(self): # Temp trainer for grabbing datasets, etc tt = self.trainer_template tt.init_data() # Combine official train & val sets X = torch.cat( [tt.dl_train.dataset.tensors[0], tt.dl_val.dataset.tensors[0]]) ys = [ torch.cat([yt, yv]) for yt, yv in zip( tt.dl_train.dataset.tensors[1:], tt.dl_val.dataset.tensors[1:]) ] # make folds fold_len = int(np.ceil(len(X) / self.n_folds)) self.X_folds = torch.split(X, fold_len) self.ys_folds = [torch.split(y, fold_len) for y in ys] trainers = [] for i_val_fold in range(self.n_folds): trainer = Trainer( validation_fold=i_val_fold, name=f"{self.name}/{i_val_fold}", **self.config, ) trainer.dl_test = tt.dl_test trainers.append(trainer) return trainers model: models.BaseNet = t.Instance(torch.nn.Module, transient=True) def _model_default(self): model = models.FilterNetEnsemble() model.set_models([trainer.model for trainer in self.trainers]) return model model_path: str = t.Str() def _model_path_default(self): return f"saved_models/{self.name}/" def init_data(self): # Initiate loading of datasets, model pass # for trainer in self.trainers: # trainer.init_data() def init_train(self): pass # for trainer in self.trainers: # trainer.init_train() def train(self, max_epochs=50): """ A pretty standard training loop, constrained to stop in `max_epochs` but may stop early if our custom stopping metric does not improve for `self.patience` epochs. Always checkpoints when a new best stopping_metric is achieved. An alternative to using ray.tune for training.""" for trainer in self.trainers: # Add data to trainer X_train = torch.cat([ arr for i, arr in enumerate(self.X_folds) if i != trainer.validation_fold ]) ys_train = [ torch.cat([ arr for i, arr in enumerate(y) if i != trainer.validation_fold ]) for y in self.ys_folds ] X_val = torch.cat([ arr for i, arr in enumerate(self.X_folds) if i == trainer.validation_fold ]) ys_val = [ torch.cat([ arr for i, arr in enumerate(y) if i == trainer.validation_fold ]) for y in self.ys_folds ] trainer.dl_train = DataLoader( TensorDataset(torch.Tensor(X_train), *ys_train), batch_size=trainer.batch_size, shuffle=True, ) trainer.data_spec = self.trainer_template.data_spec trainer.epoch_iters = self.trainer_template.epoch_iters trainer.dl_val = DataLoader( TensorDataset(torch.Tensor(X_val), *ys_val), batch_size=trainer.batch_size, shuffle=False, ) # Now clear local vars to save ranm X_train = ys_train = X_val = ys_val = None trainer.init_data() trainer.init_train() trainer.train(max_epochs=max_epochs) # Clear trainer train and val datasets to save ram trainer.dl_train = t.Undefined trainer.dl_val = t.Undefined print(f"RESTORING TO best model") trainer._restore() trainer._save() trainer.print_train_summary() em = EvalModel(trainer=trainer) em.run_test_set() em.calc_metrics() em.calc_ward_metrics() print(em.classification_report_df.to_string(float_format="%.3f")) em._save() def print_train_summary(self): for trainer in self.trainers: trainer.print_train_summary() def _save(self, checkpoint_dir=None, save_model=True, save_trainer=True): """ Saves/checkpoints model state and training state to disk. """ if checkpoint_dir is None: checkpoint_dir = self.model_path else: self.model_path = checkpoint_dir os.makedirs(checkpoint_dir, exist_ok=True) # save model params model_path = os.path.join(checkpoint_dir, "model.pth") trainer_path = os.path.join(checkpoint_dir, "trainer.pth") if save_model: torch.save(self.model.state_dict(), model_path) if save_trainer: with open(trainer_path, "wb") as f: pickle.dump(self, f) return checkpoint_dir def _restore(self, checkpoint_dir=None): """ Restores model state and training state from disk. """ if checkpoint_dir is None: checkpoint_dir = self.model_path model_path = os.path.join(checkpoint_dir, "model.pth") trainer_path = os.path.join(checkpoint_dir, "trainer.pth") # Reconstitute old trainer and copy state to this trainer. with open(trainer_path, "rb") as f: other_trainer = pickle.load(f) self.__setstate__(other_trainer.__getstate__()) # Load sub-models for trainer in self.trainers: trainer._restore() # Load model (after loading state in case we need to re-initialize model from config) self.model.load_state_dict( torch.load(model_path, map_location=self.device))
class XDomain(BMCSLeafNode, Vis2D): '''Represent the current crack state. This objects represents the discretization of the domain using segments / finite elements ''' node_name = 'crack domain' ts = tr.WeakRef #========================================================================= # Primary state variables #========================================================================= x_rot_1 = tr.Float(50, state_changed=True) theta = tr.Float(0, state_changed=True) @tr.on_trait_change('+state_changed') def set_state_changed(self): self.state_changed = True #========================================================================= # Discretization parameters #========================================================================= n_J = tr.Int(10) '''Number of nodes along the uncracked zone ''' n_m = tr.Int(8) '''Number of integration points within a segment ''' tree_view = ui.View( ui.Item('L_fps', style='readonly'), ui.Item('n_m', style='readonly'), ui.Item('n_J', style='readonly'), ui.Item('theta', style='readonly'), ui.Item('x_tip_a', style='readonly'), ui.Item('x_rot_a', style='readonly'), ) eta = tr.DelegatesTo('ts') #========================================================================= # Geometry - used for visualization and constraits of crack propagation. #========================================================================= x_Ca = tr.Property(depends_on='input_changed') '''Corner coordinates''' @tr.cached_property def _get_x_Ca(self): L = self.ts.L H = self.ts.H return np.array([[0, L, L, 0], [0, 0, H, H]], dtype=np.float_).T C_Li = tr.Property(depends_on='input_changed') '''Lines''' @tr.cached_property def _get_C_Li(self): return np.array([[0, 1], [1, 2], [2, 3], [3, 0]], dtype=np.int_) #========================================================================= # Discretization / state domain #========================================================================= state_changed = tr.Event '''Register the state change event to trigger recalculation. ''' x_t_Ia = tr.Array(dtype=np.float_, value=[]) def _x_t_Ia_default(self): return np.array([[self.ts.initial_crack_position, 0]], dtype=np.float_) x_Ia = tr.Property(depends_on='state_changed') '''Nodes along the crack path including the fps segment''' @tr.cached_property def _get_x_Ia(self): return np.vstack([self.x_t_Ia, self.x_tip_a[np.newaxis, :]]) I_Li = tr.Property(depends_on='state_changed') '''Crack segments''' @tr.cached_property def _get_I_Li(self): N_I = np.arange(len(self.x_Ia)) I_Li = np.array([N_I[:-1], N_I[1:]], dtype=np.int_).T return I_Li x_Ja = tr.Property(depends_on='state_changed') '''Uncracked vertical section''' @tr.cached_property def _get_x_Ja(self): x_J_1 = np.linspace(self.x_Ia[-1, 1], self.x_Ca[-1, 1], self.n_J) return np.c_[self.x_Ia[-1, 0] * np.ones_like(x_J_1), x_J_1] xx_Ka = tr.Property(depends_on='state_changed') '''Integrated section''' @tr.cached_property def _get_xx_Ka(self): return np.concatenate([self.x_Ia, self.x_Ja[1:]], axis=0) x_Ka = tr.Property(depends_on='state_changed') '''Integration points''' @tr.cached_property def _get_x_Ka(self): eta_m = np.linspace(0, 1, self.n_m) d_La = self.xx_Ka[1:] - self.xx_Ka[:-1] d_Kma = np.einsum('Ka,m->Kma', d_La, eta_m) x_Kma = self.xx_Ka[:-1, np.newaxis, :] + d_Kma return np.vstack([x_Kma[:, :-1, :].reshape(-1, 2), self.xx_Ka[[-1], :]]) K_Li = tr.Property(depends_on='state_changed') '''Crack segments''' @tr.cached_property def _get_K_Li(self): N_K = np.arange(len(self.x_Ka)) K_Li = np.array([N_K[:-1], N_K[1:]], dtype=np.int_).T return K_Li x_Lb = tr.Property(depends_on='state_changed') '''Midpoints''' @tr.cached_property def _get_x_Lb(self): return np.sum(self.x_Ka[self.K_Li], axis=1) / 2 L_fps = tr.Property(tr.Float) '''Length of the fracture process segment. ''' def _get_L_fps(self): return self.ts.mm.L_fps #========================================================================= # Fracture process segment #========================================================================= T_fps_a = tr.Property(tr.Array, depends_on='state_changed') '''Orientation matrix of the crack propagation segment ''' @tr.cached_property def _get_T_fps_a(self): return np.array([-np.sin(self.theta), np.cos(self.theta)], dtype=np.float_) x_tip_a = tr.Property(tr.Array, depends_on='state_changed') '''Unknown position of the crack tip. Depends on the sought fracture process segment orientation $\theta$ ''' @tr.cached_property def _get_x_tip_a(self): return self.x_fps_a + self.L_fps * self.T_fps_a da_fps = tr.Property(tr.Float, depends_on='input_changed') '''Crack length increment contoling the calculation ''' @tr.cached_property def _get_da_fps(self): return self.eta * self.L_fps x1_fps_a = tr.Property(tr.Array, depends_on='state_changed') '''Shifted position of the fracture process hot spot. This is the point that is required to achieve the tensile strength ''' @tr.cached_property def _get_x1_fps_a(self): x1_fps_a = self.x_fps_a + self.da_fps * self.T_fps_a x1_fps_a[1] = np.min([x1_fps_a[1], self.ts.H]) return x1_fps_a x_fps_a = tr.Property(tr.Array, depends_on='state_changed') '''Position of the starting point of the fracture process segment. ''' @tr.cached_property def _get_x_fps_a(self): return self.x_t_Ia[-1, :] #========================================================================= # Transformation relative to the crack path #========================================================================= norm_n_vec_L = tr.Property(depends_on='state_changed') '''Unit line vector ''' @tr.cached_property def _get_norm_n_vec_L(self): K_Li = self.K_Li x_Lia = self.x_Ka[K_Li] n_vec_La = x_Lia[:, 1, :] - x_Lia[:, 0, :] return np.sqrt(np.einsum('...a,...a->...', n_vec_La, n_vec_La)) T_Lab = tr.Property(depends_on='state_changed') '''Unit line vector ''' @tr.cached_property def _get_T_Lab(self): K_Li = self.K_Li x_Lia = self.x_Ka[K_Li] line_vec_La = x_Lia[:, 1, :] - x_Lia[:, 0, :] norm_line_vec_L = np.sqrt(np.einsum('...a,...a->...', line_vec_La, line_vec_La)) normed_line_vec_La = np.einsum('...a,...->...a', line_vec_La, 1. / norm_line_vec_L) t_vec_La = np.einsum('ijk,...j,k->...i', EPS[:-1, :-1, :], normed_line_vec_La, Z) T_bLa = np.array([t_vec_La, normed_line_vec_La]) T_Lab = np.einsum('bLa->Lab', T_bLa) return T_Lab plot_scale = tr.Float(1.0, auto_set=False, enter_set=True) x_n_ejaM = tr.Property(depends_on='state_changed') '''Unit line vector ''' @tr.cached_property def _get_x_n_ejaM(self): K_Li = self.K_Li T_Lab = self.T_Lab x_Lia = self.x_Ka[K_Li] x_n0_Mea = np.einsum( 'e,Ma->Mae', np.ones((2,)), np.sum(x_Lia, axis=1) / 2) x_n_jMea = np.array( [x_n0_Mea, x_n0_Mea + self.plot_scale * T_Lab] ) return np.einsum('jMae->ejaM', x_n_jMea) def get_rot_mtx(self, phi): return np.array( [[np.cos(phi), -np.sin(phi)], [np.sin(phi), np.cos(phi)]], dtype=np.float_) x_rot_a = tr.Property(depends_on='state_changed') '''Center of rotation. ''' @tr.cached_property def _get_x_rot_a(self): x_rot_0 = self.x_tip_a[0] x_rot_1 = self.x_rot_1 return np.array([x_rot_0, x_rot_1], dtype=np.float_) def rotate(self, x_Ka, phi): rot_mtx = self.get_rot_mtx(phi) u_Ib = np.einsum( 'ab,...b->...a', rot_mtx, x_Ka - self.x_rot_a ) return u_Ib + self.x_rot_a w_f_t = tr.DelegatesTo('sim') #========================================================================= # Unknown variables #========================================================================= phi = tr.Property(tr.Float, depends_on='state_changed') '''Rotation of the right-hand part of the shear zone. ''' @tr.cached_property def _get_phi(self): x_fps_a = self.x_fps_a w_f_t = self.ts.w_f_t x_rot_a = self.x_rot_a n_fps = len(self.x_t_Ia) - 1 n_m_fps = n_fps * (self.n_m - 1) + 3 T_ab1 = self.T_Lab[n_m_fps, ...] theta = self.theta T_ab = np.array([[np.cos(theta), np.sin(theta)], [-np.sin(theta), np.cos(theta)]]) phi = get_phi(T_ab, x_fps_a, x_rot_a, w_f_t) return phi def get_moved_Ka(self, x_Ka): return self.rotate(x_Ka=x_Ka, phi=self.phi) x1_Ia = tr.Property(depends_on='state_changed') '''Displaced segment nodes''' @tr.cached_property def _get_x1_Ia(self): return self.get_moved_Ka(self.x_Ia) x1_Ka = tr.Property(depends_on='state_changed') '''Displaced integration points''' @tr.cached_property def _get_x1_Ka(self): return self.get_moved_Ka(self.x_Ka) x1_Ca = tr.Property(depends_on='state_changed') '''Diplaced corner nodes''' @tr.cached_property def _get_x1_Ca(self): return self.get_moved_Ka(self.x_Ca) #========================================================================= # Control and state variables #========================================================================= v = tr.Property(depends_on='state_changed') '''Vertical displacement on the right hand side ''' @tr.cached_property def _get_v(self): x_rot_0 = self.x_rot_a[0] phi = self.phi L_rot_distance = self.ts.L - x_rot_0 v = L_rot_distance * np.sin(phi) return v #========================================================================= # Kinematics #========================================================================= u_Lib = tr.Property(depends_on='state_changed') '''Displacement of lines in local coordinates''' @tr.cached_property def _get_u_Lib(self): K_Li = self.K_Li u_Ka = self.x1_Ka - self.x_Ka u_Lia = u_Ka[K_Li] T_Lab = self.T_Lab u_Lib = np.einsum( 'Lia,Lab->Lib', u_Lia, T_Lab ) return u_Lib u_Lb = tr.Property(depends_on='state_changed') '''Displacement of the segment midpoints ''' @tr.cached_property def _get_u_Lb(self): K_Li = self.K_Li u_Ka = self.x1_Ka - self.x_Ka u_Lia = u_Ka[K_Li] u_La = np.sum(u_Lia, axis=1) / 2 T_Lab = self.T_Lab u_Lb = np.einsum( 'La,Lab->Lb', u_La, T_Lab ) return u_Lb get_u0 = tr.Property(depends_on='state_changed') '''Get an interpolator function returning horizontal displacement component for a specified vertical coordinate of a ligmant. ''' @tr.cached_property def _get_get_u0(self): return interp1d(self.x_Lb[:, 1], self.u_Lb[:, 0], fill_value='extrapolate') u_ejaM = tr.Property(depends_on='state_changed') '''Transformed displacement at the line midpoints ''' @tr.cached_property def _get_u_ejaM(self): return self.get_f_ejaM(self.u_Lb) def get_f_ejaM(self, f_Lb, scale=1.0): K_Li = self.K_Li T_Lab = self.T_Lab x_Lia = self.x_Ka[K_Li] x_n0_Mea = np.einsum( 'e,Ma->Mae', np.ones((2,)), np.sum(x_Lia, axis=1) / 2) f_base_Lab = np.einsum( 'Lb, Lab->Lab', f_Lb, scale * T_Lab ) x_n_jMea = np.array( [x_n0_Mea, x_n0_Mea + f_base_Lab] ) return np.einsum('jMae->ejaM', x_n_jMea) cmod = tr.Property(depends_on='state_changed') '''Crack mouth opening displacement . ''' @tr.cached_property def _get_cmod(self): return self.u_Lb[0, 0] xn_m_fps = tr.Property(depends_on='state_changed') '''Get the index of the material point at which the orientation is evaluated ''' @tr.cached_property def _get_xn_m_fps(self): n_tip = len(self.x_t_Ia) - 1 n_m_tip = n_tip * (self.n_m - 1) + int(self.n_m * 2) return n_m_tip x_sig_fps_1 = tr.Property(depends_on='state_changed') '''Get the vertical position of the orientation segment ''' @tr.cached_property def _get_x_sig_fps_1(self): return self.x_tip_a[1] #========================================================================= # Plot current state #========================================================================= def plot_x_Ka(self, ax): x_aK = self.x_Ka.T ax.plot(*x_aK, 'bo', color='blue', markersize=8) def plot_x_tip_a(self, ax): '''Show the current crack tip. ''' x, y = self.x_tip_a ax.plot(x, y, 'bo', color='red', markersize=10) def plot_x_rot_a(self, ax): '''Show the current center of rotation. ''' x, y = self.x_rot_a ax.annotate('center of rotation', xy=(x, y), xytext=(x + 50, y + 20), arrowprops=dict(facecolor='black', width=1, shrink=0.1), ) ax.plot([0, self.ts.L], [y, y], color='black', linestyle='--') ax.plot(x, y, 'bo', color='blue', markersize=10) def plot_x_fps_a(self, ax): x, y = self.x_fps_a ax.plot(x, y, 'bo', color='green', markersize=10) def plot_sz0(self, ax): x_Ia = self.x_Ia x_Ca = self.x_Ca x_aI = x_Ia.T x_LL = x_Ca[0] x_LU = x_Ca[3] x_RL = self.x_Ka[0] x_RU = self.x_Ka[-1] x_Da = np.array([x_LL, x_RL, x_RU, x_LU]) D_Li = np.array([[0, 1], [2, 3], [3, 0]], dtype=np.int_) x_aiD = np.einsum('Dia->aiD', x_Da[D_Li]) ax.plot(*x_aiD, color='black') ax.plot(*x_aI, lw=2, color='black') def plot_sz1(self, ax): x_Ia = self.x1_Ia x_Ca = self.x1_Ca x_aI = x_Ia.T x_LL = self.x1_Ka[0] x_LU = self.x1_Ka[-1] x_RL = x_Ca[1] x_RU = x_Ca[2] x_Da = np.array([x_LL, x_RL, x_RU, x_LU]) D_Li = np.array([[0, 1], [1, 2], [2, 3], ], dtype=np.int_) x_aiD = np.einsum('Dia->aiD', x_Da[D_Li]) ax.plot(*x_aiD, color='black') ax.set_title(r'Simulated crack path') ax.set_xlabel(r'Horizontal position $x$ [mm]') ax.set_ylabel(r'Vertical position $z$ [mm]') ax.plot(*x_aI, lw=2, color='black') def plot_sz_fill(self, ax): x_Ca = self.x1_Ca x_Da = np.vstack([ x_Ca[:1], self.x_Ia, self.x1_Ia[::-1], x_Ca[1:, :], ]) ax.fill(*x_Da.T, color='gray', alpha=0.2) def plot_reinf(self, ax): for z in self.ts.z_f: # left part ax.plot([0, self.ts.L], [z, z], color='maroon', lw=5) def plot_sz_state(self, ax, vot=1.0): self.plot_sz1(ax) self.plot_sz0(ax) self.plot_sz_fill(ax) self.plot_x_tip_a(ax) self.plot_x_rot_a(ax) self.plot_x_fps_a(ax) self.plot_reinf(ax) ax.axis('off') ax.axis('equal') def plot_T_Lab(self, ax): K_Li = self.K_Li x_n_ejaM = self.x_n_ejaM x_n_eajM = np.einsum('ejaM->eajM', x_n_ejaM) ax.plot(*x_n_eajM.reshape(-1, 2, len(K_Li)), color='orange', lw=3) def plot_u_T_Lab(self, ax): u_ejaM = self.u_ejaM u_eajM = np.einsum('ejaM->eajM', u_ejaM) ax.plot(*u_eajM.reshape(-1, 2, len(self.K_Li)), color='orange', lw=3) def plot_hlines(self, ax, h_min, h_max): _, y_tip = self.x_tip_a _, y_rot = self.x_rot_a _, z_fps = self.x_fps_a ax.plot([h_min, h_max], [y_tip, y_tip], color='black', linestyle=':') ax.plot([h_min, h_max], [y_rot, y_rot], color='black', linestyle='--') ax.plot([h_min, h_max], [z_fps, z_fps], color='black', linestyle='-.') def plot_u_Lc(self, ax, u_Lc, idx=0, color='black', label=r'$w$ [mm]'): x_La = self.x_Lb u_Lb_min = np.min(u_Lc[:, idx]) u_Lb_max = np.max(u_Lc[:, idx]) self.plot_hlines(ax, u_Lb_min, u_Lb_max) ax.plot(u_Lc[:, idx], x_La[:, 1], color=color, label=label) ax.fill_betweenx(x_La[:, 1], u_Lc[:, idx], 0, color=color, alpha=0.1) ax.set_xlabel(label) ax.legend(loc='lower left') def plot_u_L0(self, ax, vot=1): self.plot_u_Lc(ax, self.u_Lb, 0, label=r'$w$ [mm]') ax.set_xlabel(r'effective horizontal COD $w$ [mm]')
class EvalModel(t.HasStrictTraits): trainer: Trainer = t.Any() model: mo.BaseNet = t.DelegatesTo("trainer") dl_test: DataLoader = t.DelegatesTo("trainer") data_spec: dict = t.DelegatesTo("trainer") cuda: bool = t.DelegatesTo("trainer") device: str = t.DelegatesTo("trainer") loss_func: str = t.DelegatesTo("trainer") model_path: str = t.DelegatesTo("trainer") has_null_class: bool = t.DelegatesTo("trainer") predict_null_class: bool = t.DelegatesTo("trainer") # 'prediction' mode employs overlap and reconstructs signal # as a contiguous timeseries w/ optional windowing. # It aims for best accuracy/f1 by using overlap, and will # typically outperform 'training' mode. # 'training' mode does not average repeated point and does # not window; it should product acc/loss/f1 similar to # training mode. run_mode: str = t.Enum(["prediction", "training"]) window: str = t.Enum(["hanning", "boxcar"]) eval_batch_size: int = t.Int(100) target_names: ty.List[str] = t.ListStr() def _target_names_default(self): target_names = self.data_spec["output_spec"][0]["classes"] if self.has_null_class: assert target_names[0] in ("", "Null") if not self.predict_null_class: target_names = target_names[1:] return target_names def _run_model_on_batch(self, data, targets): targets = torch.stack(targets) if self.cuda: data, targets = data.cuda(), targets.cuda() output = self.model(data) _targets = self.model.transform_targets(targets, one_hot=False) if self.loss_func == "cross_entropy": _losses = [F.cross_entropy(o, t) for o, t in zip(output, _targets)] loss = sum(_losses) elif self.loss_func == "binary_cross_entropy": _targets_onehot = self.model.transform_targets(targets, one_hot=True) _losses = [ F.binary_cross_entropy_with_logits(o, t) for o, t in zip(output, _targets_onehot) ] loss = sum(_losses) else: raise NotImplementedError(self.loss) # Assume only 1 output: return loss, output[0], _targets[0], _losses[0] def run_test_set(self, dl=None): """ Runs `self.model` on `self.dl_test` (or a provided dl) and stores results for subsequent evaluation. """ if dl is None: dl = self.dl_test if self.cuda: self.model.cuda() self.model.eval() if self.eval_batch_size: dl = DataLoader(dl.dataset, batch_size=self.eval_batch_size, shuffle=False) # # # Xc, yc = data.get_x_y_contig('test') X, *ys = dl.dataset.tensors # X: [N, input_chans, win_len] step = int(X.shape[2] / 2) assert torch.equal(X[0, :, step], X[1, :, 0]) losses = [] outputsraw = [] outputs = [] targets = [] with Timer("run", log_output=False) as tr: with Timer("infer", log_output=False) as ti: for batch_idx, (data, *target) in enumerate(dl): ( batch_loss, batch_output, batch_targets, train_losses, ) = self._run_model_on_batch(data, target) losses.append(batch_loss.detach().cpu().item()) outputsraw.append(batch_output.detach().cpu().data.numpy()) outputs.append( torch.argmax(batch_output, 1, False).detach().cpu().data.numpy()) targets.append(batch_targets.detach().cpu().data.numpy()) self.infer_time_s_cpu = ti.interval_cpu self.infer_time_s_wall = ti.interval_wall self.loss = np.mean(losses) targets = np.concatenate(targets, axis=0) # [N, out_win_len] outputsraw = np.concatenate( outputsraw, axis=0) # [N, n_out_classes, out_win_len] outputs = np.concatenate(outputs, axis=0) # [N, n_out_classes, out_win_len] # win_len = toutputsraw[0].shape[-1] if (self.model.output_type == "many_to_one_takelast" or self.run_mode == "training"): self.targets = np.concatenate(targets, axis=-1) # [N,] self.outputsraw = np.concatenate( outputsraw, axis=-1) # [n_out_classes, N,] self.outputs = np.concatenate(outputs, axis=-1) # [N,] elif self.run_mode == "prediction": n_segments, n_classes, out_win_len = outputsraw.shape output_step = int(out_win_len / 2) if self.window == "hanning": EPS = 0.001 # prevents divide-by-zero arr_window = (1 - EPS) * np.hanning(out_win_len) + EPS elif self.window == "boxcar": arr_window = np.ones((out_win_len, )) else: raise ValueError() # Allocate space for merged predictions if self.has_null_class and not self.predict_null_class: outputsraw2 = np.zeros( (n_segments + 1, n_classes - 1, output_step, 2)) window2 = np.zeros( (n_segments + 1, n_classes - 1, output_step, 2)) # [N+1, out_win_len/2, 2] # Drop in outputs/window vals in the two layers outputsraw = outputsraw[:, 1:, :] else: outputsraw2 = np.zeros( (n_segments + 1, n_classes, output_step, 2)) window2 = np.zeros((n_segments + 1, n_classes, output_step, 2)) # [N+1, out_win_len/2, 2] # Drop in outputs/window vals in the two layers outputsraw2[:-1, :, :, 0] = outputsraw[:, :, :output_step] outputsraw2[1:, :, :, 1] = outputsraw[:, :, output_step:output_step * 2] window2[:-1, :, :, 0] = arr_window[:output_step] window2[1:, :, :, 1] = arr_window[output_step:output_step * 2] merged_outputsraw = (outputsraw2 * window2).sum( axis=3) / (window2).sum(axis=3) softmaxed_merged_outputsraw = softmax(merged_outputsraw, axis=1) merged_outputs = np.argmax(softmaxed_merged_outputsraw, 1) self.outputsraw = np.concatenate(merged_outputsraw, axis=-1) self.outputs = np.concatenate(merged_outputs, axis=-1) self.targets = np.concatenate( np.concatenate( [ targets[:, :output_step], targets[[-1], output_step:output_step * 2], ], axis=0, ), axis=-1, ) else: raise ValueError() if self.has_null_class and not self.predict_null_class: not_null_mask = self.targets > 0 self.outputsraw = self.outputsraw[..., not_null_mask] self.outputs = self.outputs[not_null_mask] self.targets = self.targets[not_null_mask] self.targets -= 1 self.n_samples_in = np.prod(dl.dataset.tensors[1].shape) self.n_samples_out = len(self.outputs) self.infer_samples_per_s = self.n_samples_in / self.infer_time_s_wall self.run_time_s_cpu = tr.interval_cpu self.run_time_s_wall = tr.interval_wall loss: float = t.Float() targets: np.ndarray = t.Array() outputsraw: np.ndarray = t.Array() outputs: np.ndarray = t.Array() n_samples_in: int = t.Int() n_samples_out: int = t.Int() infer_samples_per_s: float = t.Float() infer_time_s_cpu: float = t.Float() infer_time_s_wall: float = t.Float() run_time_s_cpu: float = t.Float() run_time_s_wall: float = t.Float() extra: dict = t.Dict({}) acc: float = t.Float() f1: float = t.Float() f1_mean: float = t.Float() event_f1: float = t.Float() classification_report_txt: str = t.Str() classification_report_dict: dict = t.Dict() classification_report_df: pd.DataFrame = t.Property( t.Instance(pd.DataFrame)) confusion_matrix: np.ndarray = t.Array() nonull_acc: float = t.Float() nonull_f1: float = t.Float() nonull_f1_mean: float = t.Float() nonull_classification_report_txt: str = t.Str() nonull_classification_report_dict: dict = t.Dict() nonull_classification_report_df: pd.DataFrame = t.Property( t.Instance(pd.DataFrame)) nonull_confusion_matrix: np.ndarray = t.Array() def calc_metrics(self): self.acc = sklearn.metrics.accuracy_score(self.targets, self.outputs) self.f1 = sklearn.metrics.f1_score(self.targets, self.outputs, average="weighted") self.f1_mean = sklearn.metrics.f1_score(self.targets, self.outputs, average="macro") self.classification_report_txt = sklearn.metrics.classification_report( self.targets, self.outputs, digits=3, labels=np.arange(len(self.target_names)), target_names=self.target_names, ) self.classification_report_dict = sklearn.metrics.classification_report( self.targets, self.outputs, digits=3, output_dict=True, labels=np.arange(len(self.target_names)), target_names=self.target_names, ) self.confusion_matrix = sklearn.metrics.confusion_matrix( self.targets, self.outputs) # Now, ignoring the null/none class: if self.has_null_class and self.predict_null_class: # assume null class comes fistnonull_mask = self.targets > 0 nonull_mask = self.targets > 0 nonull_targets = self.targets[nonull_mask] # nonull_outputs = self.outputs[nonull_mask] nonull_outputs = self.outputsraw[1:, :].argmax( axis=0)[nonull_mask] + 1 self.nonull_acc = sklearn.metrics.accuracy_score( nonull_targets, nonull_outputs) self.nonull_f1 = sklearn.metrics.f1_score(nonull_targets, nonull_outputs, average="weighted") self.nonull_f1_mean = sklearn.metrics.f1_score(nonull_targets, nonull_outputs, average="macro") self.nonull_classification_report_txt = sklearn.metrics.classification_report( nonull_targets, nonull_outputs, digits=3, labels=np.arange(len(self.target_names)), target_names=self.target_names, ) self.nonull_classification_report_dict = sklearn.metrics.classification_report( nonull_targets, nonull_outputs, digits=3, output_dict=True, labels=np.arange(len(self.target_names)), target_names=self.target_names, ) self.nonull_confusion_matrix = sklearn.metrics.confusion_matrix( nonull_targets, nonull_outputs) else: self.nonull_acc = self.acc self.nonull_f1 = self.f1 self.nonull_f1_mean = self.f1_mean self.nonull_classification_report_txt = self.classification_report_txt self.nonull_classification_report_dict = self.classification_report_dict self.nonull_confusion_matrix = self.confusion_matrix ward_metrics: WardMetrics = t.Instance(WardMetrics) def calc_ward_metrics(self): """ Do event-wise metrics, using the `wardmetrics` package which implements metrics from: [1] J. A. Ward, P. Lukowicz, and H. W. Gellersen, “Performance metrics for activity recognition,” ACM Trans. Intell. Syst. Technol., vol. 2, no. 1, pp. 1–23, Jan. 2011. """ import wardmetrics # Must be in prediction mode -- otherwise, data is not contiguous, ward metrics will be bogus assert self.run_mode == "prediction" targets = self.targets predictions = self.outputs wmetrics = WardMetrics() targets_events = wardmetrics.frame_results_to_events(targets) preds_events = wardmetrics.frame_results_to_events(predictions) for i, class_name in enumerate(self.target_names): class_wmetrics = ClassWardMetrics() t = targets_events.get(str(i), []) p = preds_events.get(str(i), []) # class_wmetrics['t'] = t # class_wmetrics['p'] = p try: assert len(t) and len(p) ( twoset_results, segments_with_scores, segment_counts, normed_segment_counts, ) = wardmetrics.eval_segments(t, p) class_wmetrics.segment_twoset_results = twoset_results ( gt_event_scores, det_event_scores, detailed_scores, standard_scores, ) = wardmetrics.eval_events(t, p) class_wmetrics.event_detailed_scores = detailed_scores class_wmetrics.event_standard_scores = standard_scores except (AssertionError, ZeroDivisionError) as e: class_wmetrics.segment_twoset_results = {} class_wmetrics.event_detailed_scores = {} class_wmetrics.event_standard_scores = {} # print("Empty Results or targets for a class.") # raise ValueError("Empty Results or targets for a class.") wmetrics.class_ward_metrics.append(class_wmetrics) tt = [] pp = [] for i, class_name in enumerate(self.target_names): # skip null class for combined eventing: if class_name in ("", "Null"): continue if len(tt) or len(pp): offset = np.max(tt + pp) + 2 else: offset = 0 [(a + offset, b + offset) for (a, b) in t] t = targets_events.get(str(i), []) p = preds_events.get(str(i), []) tt += [(a + offset, b + offset) for (a, b) in t] pp += [(a + offset, b + offset) for (a, b) in p] t = tt p = pp class_wmetrics = ClassWardMetrics() assert len(t) and len(p) ( twoset_results, segments_with_scores, segment_counts, normed_segment_counts, ) = wardmetrics.eval_segments(t, p) class_wmetrics.segment_twoset_results = twoset_results ( gt_event_scores, det_event_scores, detailed_scores, standard_scores, ) = wardmetrics.eval_events(t, p) class_wmetrics.event_detailed_scores = detailed_scores class_wmetrics.event_standard_scores = standard_scores # Reformat as dataframe for easier calculations df = pd.DataFrame( [cm.event_standard_scores for cm in wmetrics.class_ward_metrics], index=self.target_names, ) df.loc["all_nonull"] = class_wmetrics.event_standard_scores # Calculate F1's to summarize recall/precision for each class df["f1"] = (2 * (df["precision"] * df["recall"]) / (df["precision"] + df["recall"])) df["f1 (weighted)"] = ( 2 * (df["precision (weighted)"] * df["recall (weighted)"]) / (df["precision (weighted)"] + df["recall (weighted)"])) # Load dataframes into dictionary output wmetrics.df_event_scores = df wmetrics.df_event_detailed_scores = pd.DataFrame( [cm.event_detailed_scores for cm in wmetrics.class_ward_metrics], index=self.target_names, ) wmetrics.df_segment_2set_results = pd.DataFrame( [cm.segment_twoset_results for cm in wmetrics.class_ward_metrics], index=self.target_names, ) wmetrics.overall_ward_metrics = class_wmetrics self.ward_metrics = wmetrics self.event_f1 = self.ward_metrics.df_event_scores.loc["all_nonull", "f1"] def _get_classification_report_df(self): df = pd.DataFrame(self.classification_report_dict).T # Include Ward-metrics-derived "Event F1 (unweighted by length)" if self.ward_metrics: df["event_f1"] = self.ward_metrics.df_event_scores["f1"] else: df["event_f1"] = np.nan # Calculate various summary averages df.loc["macro avg", "event_f1"] = df["event_f1"].iloc[:-3].mean() df.loc["weighted avg", "event_f1"] = ( df["event_f1"].iloc[:-3] * df["support"].iloc[:-3]).sum() / df["support"].iloc[:-3].sum() df["support"] = df["support"].astype(int) return df def _get_nonull_classification_report_df(self): target_names = self.target_names if not (target_names[0] in ("", "Null")): return None df = pd.DataFrame(self.nonull_classification_report_dict).T df["support"] = df["support"].astype(int) return df def _save(self, checkpoint_dir=None): """ Saves/checkpoints model state and training state to disk. """ if checkpoint_dir is None: checkpoint_dir = self.model_path os.makedirs(checkpoint_dir, exist_ok=True) # save model params evalmodel_path = os.path.join(checkpoint_dir, "evalmodel.pth") with open(evalmodel_path, "wb") as f: pickle.dump(self, f) return checkpoint_dir def _restore(self, checkpoint_dir=None): """ Restores model state and training state from disk. """ if checkpoint_dir is None: checkpoint_dir = self.model_path evalmodel_path = os.path.join(checkpoint_dir, "evalmodel.pth") # Reconstitute old trainer and copy state to this trainer. with open(evalmodel_path, "rb") as f: other_evalmodel = pickle.load(f) self.__setstate__(other_evalmodel.__getstate__()) self.trainer._restore(checkpoint_dir)
class WBTessellationBase(bu.Model): name = 'WB Tessellation Base' plot_backend = 'k3d' # show_wireframe = bu.Bool(True, GEO=True) show_node_labels = bu.Bool(False, GEO=True) wb_cell = bu.EitherType(options=[('WBCell4Param', WBCell4Param), ('WBCell5Param', WBCell5Param), ('WBCell5ParamV2', WBCell5ParamV2), ('WBCell5ParamV3', WBCell5ParamV3)], GEO=True) X_Ia = tr.DelegatesTo('wb_cell_') I_Fi = tr.DelegatesTo('wb_cell_') tree = ['wb_cell'] event_geo = bu.Bool(True, 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( bu.Item('wb_cell'), # bu.Item('show_wireframe'), bu.Item('show_node_labels'), ) def _get_br_X_Ia(self, X_Ia, rot=None): br_X_Ia = self._get_cell_matching_v1_to_v2(X_Ia, np.array([4, 6]), np.array([5, 1])) return self.rotate_cell(br_X_Ia, np.array([4, 6]), self.sol[0] if rot is None else rot) def _get_ur_X_Ia(self, X_Ia, rot=None): ur_X_Ia = self._get_cell_matching_v1_to_v2(X_Ia, np.array([6, 2]), np.array([3, 5])) return self.rotate_cell(ur_X_Ia, np.array([6, 2]), self.sol[1] if rot is None else rot) def _get_ul_X_Ia(self, X_Ia, rot=None): br_X_Ia = self._get_cell_matching_v1_to_v2(X_Ia, np.array([5, 1]), np.array([4, 6])) return self.rotate_cell(br_X_Ia, np.array([5, 1]), -self.sol[0] if rot is None else rot) def _get_bl_X_Ia(self, X_Ia, rot=None): br_X_Ia = self._get_cell_matching_v1_to_v2(X_Ia, np.array([3, 5]), np.array([6, 2])) return self.rotate_cell(br_X_Ia, np.array([3, 5]), -self.sol[1] if rot is None else rot) def _get_cell_matching_v1_to_v2(self, X_Ia, v1_ids, v2_ids): v1_2a = np.array([X_Ia[v1_ids[0]], X_Ia[v1_ids[1]], X_Ia[0]]).T v2_2a = np.array([ X_Ia[v2_ids[0]], X_Ia[v2_ids[1]], X_Ia[v2_ids[0]] + X_Ia[v2_ids[1]] - X_Ia[0] ]).T rot, trans = get_best_rot_and_trans_3d(v1_2a, v2_2a) translated_X_Ia = trans.flatten() + np.einsum('ba, Ia -> Ib', rot, X_Ia) return self.rotate_cell(translated_X_Ia, v1_ids, angle=np.pi) def rotate_cell(self, cell_X_Ia, v1_ids, angle=np.pi): # Rotating around vector ####### # 1. Bringing back to origin (because rotating is around a vector originating from origin) cell_X_Ia_copy = np.copy(cell_X_Ia) cell_X_Ia = cell_X_Ia_copy - cell_X_Ia_copy[v1_ids[1]] # 2. Rotating rot_around_v1 = get_rot_matrix_around_vector( cell_X_Ia[v1_ids[0]] - cell_X_Ia[v1_ids[1]], angle) cell_X_Ia = np.einsum('ba, Ia -> Ib', rot_around_v1, cell_X_Ia) # 3. Bringing back in position return cell_X_Ia + cell_X_Ia_copy[v1_ids[1]] sol = tr.Property(depends_on='+GEO') @tr.cached_property def _get_sol(self): # No solution is provided in base class, a default value is provided for visualization return np.array([np.pi, np.pi]) # Plotting ########################################################################## def setup_plot(self, pb): self.pb = pb pb.clear_fig() I_Fi = self.I_Fi X_Ia = self.X_Ia br_X_Ia = self._get_br_X_Ia(X_Ia) ur_X_Ia = self._get_ur_X_Ia(X_Ia) self.add_cell_to_pb(pb, X_Ia, I_Fi, 'X_Ia') self.add_cell_to_pb(pb, br_X_Ia, I_Fi, 'br_X_Ia') self.add_cell_to_pb(pb, ur_X_Ia, I_Fi, 'ur_X_Ia') k3d_mesh = {} k3d_wireframe = {} k3d_labels = {} def update_plot(self, pb): if self.k3d_mesh: X_Ia = self.X_Ia.astype(np.float32) br_X_Ia = self._get_br_X_Ia(self.X_Ia).astype(np.float32) ur_X_Ia = self._get_ur_X_Ia(self.X_Ia).astype(np.float32) self.k3d_mesh['X_Ia'].vertices = X_Ia self.k3d_mesh['br_X_Ia'].vertices = br_X_Ia self.k3d_mesh['ur_X_Ia'].vertices = ur_X_Ia self.k3d_wireframe['X_Ia'].vertices = X_Ia self.k3d_wireframe['br_X_Ia'].vertices = br_X_Ia self.k3d_wireframe['ur_X_Ia'].vertices = ur_X_Ia else: self.setup_plot(pb) def add_cell_to_pb(self, pb, X_Ia, I_Fi, obj_name): plot = pb.plot_fig wb_mesh = k3d.mesh( X_Ia.astype(np.float32), I_Fi.astype(np.uint32), # opacity=0.9, color=0x999999, side='double') rand_color = random.randint(0, 0xFFFFFF) plot += wb_mesh self.k3d_mesh[obj_name] = wb_mesh # wb_points = k3d.points(X_Ia.astype(np.float32), # color=0x999999, # point_size=100) # plot +=wb_points if self.show_node_labels: texts = [] for I, X_a in enumerate(X_Ia): k3d_text = k3d.text('%g' % I, tuple(X_a), label_box=False, size=0.8, color=rand_color) plot += k3d_text texts.append(k3d_text) self.k3d_labels[obj_name] = texts wb_mesh_wireframe = k3d.mesh(X_Ia.astype(np.float32), I_Fi.astype(np.uint32), color=0x000000, wireframe=True) plot += wb_mesh_wireframe self.k3d_wireframe[obj_name] = wb_mesh_wireframe
class ModelController(_traitsui.Controller): '''MVController base class for stat analysis model''' id = _traits.DelegatesTo('model') name = _traits.Str() plot_uis = _traits.List() win_handle = _traits.Any() # def init(self, info): # super(ModelController, self).init(info) # self.win_handle = info.ui.control def _name_default(self): raise NotImplementedError('_name_default') def __eq__(self, other): return self.id == other def __ne__(self, other): return self.id != other def get_result(self): return self.model.res def open_window(self, viewable, view_loop): """Expected viewable is by now: + Plot subtype + DataSet type """ if isinstance(viewable, PCScatterPlot): res = self.get_result() plot_control = PCPlotControl(viewable) win = SinglePlotWindow( plot=plot_control, res=res, view_loop=view_loop, ) self._show_plot_window(win) elif isinstance(viewable, _chaco.DataView): res = self.get_result() plot_control = NoPlotControl(viewable) win = SinglePlotWindow( plot=plot_control, res=res, view_loop=view_loop, ) self._show_plot_window(win) elif isinstance(viewable, DataSet): table = DSTableViewer(viewable) table.edit_traits(view=table.get_view(), kind='live', parent=self.win_handle) else: raise NotImplementedError("Do not know how to open this") def _show_plot_window(self, plot_window): # FIXME: Setting parent forcing main ui to stay behind plot windows # plot_window.mother_ref = self if sys.platform == 'linux2': self.plot_uis.append( # plot_window.edit_traits(parent=self.win_handle, kind='live') plot_window.edit_traits(kind='live')) # elif sys.platform == 'win32': else: # FIXME: Investigate more here self.plot_uis.append( plot_window.edit_traits(parent=self.win_handle, kind='live') # plot_window.edit_traits(kind='live') ) def _wind_title(self, res): mn = res.method_name return "{0} | Overview - ConsumerCheck".format(mn)
class MainWindow(tr.HasStrictTraits): forming_process_view = tr.Instance(FormingProcessView, ()) forming_task_scene = tr.Instance(FormingTaskView3D, ()) forming_process = tr.Property def _get_forming_process(self): return self.forming_process_view.forming_process def _set_forming_process(self, fp): self.forming_process_view.forming_process = fp def _selected_node_changed(self): self.selected_node.ui = self def get_vot_range(self): return self.forming_task_scene.get_vot_range() vot = tr.DelegatesTo('forming_task_scene') data_changed = tr.Event replot = tr.Button def _replot_fired(self): self.figure.clear() self.selected_node.plot(self.figure) self.data_changed = True clear = tr.Button() def _clear_fired(self): self.figure.clear() self.data_changed = True view = tu.View(tu.HSplit( tu.VGroup( tu.Item( 'forming_process_view@', id='oricreate.hsplit.left.tree.id', resizable=True, show_label=False, width=300, ), id='oricreate.hsplit.left.id', ), tu.VGroup( tu.Item( 'forming_task_scene@', show_label=False, resizable=True, id='oricreate.hsplit.viz3d.notebook.id', ), id='oricreate.hsplit.viz3d.id', label='viz sheet', ), id='oricreate.hsplit.id', ), id='oricreate.id', width=1.0, height=1.0, title='OriCreate', resizable=True, handler=TreeViewHandler(), key_bindings=key_bindings, toolbar=tu.ToolBar(*toolbar_actions, image_size=(32, 32), show_tool_names=False, show_divider=True, name='view_toolbar'), menubar=tu.MenuBar( Menu(menu_exit, Separator(), name='File'), ))
class TriXDomainMITC(XDomainFE): name = 'TriXDomainFE' ''' Finite element discretization with dofs and mappings derived from the FE definition ''' mesh = tr.Instance(FETriangularMesh) def _mesh_default(self): return FETriangularMesh(fets=FETS2DMITC()) fets = tr.DelegatesTo('mesh') tree = ['mesh'] change = tr.Event(GEO=True) plot_backend = 'k3d' n_dofs = tr.Property def _get_n_dofs(self): return len(self.mesh.X_Id) * self.mesh.n_nodal_dofs eta_w = tr.Property r'''Weight factors for numerical integration. ''' def _get_eta_w(self): return self.fets.w_m Na_deta = tr.Property() r'''Derivatives of the shape functions in the integration points. ''' @tr.cached_property def _get_Na_deta(self): return np.einsum('imr->mri', self.fets.dh_imr) x_0 = tr.Property() @tr.cached_property def _get_x_0(self): return self.mesh.X_Id x = tr.Property() @tr.cached_property def _get_x(self): return self.x_0 F = tr.Property() @tr.cached_property def _get_F(self): return self.mesh.I_Fi T_Fab = tr.Property(depends_on='+GEO') @tr.cached_property def _get_T_Fab(self): return self.F_L_bases[:, 0, :] I_Ei = tr.Property def _get_I_Ei(self): return self.F_N x_Eia = tr.Property(depends_on='+GEO') @tr.cached_property def _get_x_Eia(self): X_Eia = self.X_Id[self.I_Ei, :] X_E0a = X_Eia[:, 0, :] X_Eia -= X_E0a[:, np.newaxis, :] X_Eic = np.einsum('Eac,Eic->Eia', self.T_Fab, X_Eia) return X_Eic[...] def U2u(self, U_Eia): u0_Eia = U_Eia[...,:-1] return u0_Eia def xU2u(self, U_Eio): u1_Eia = np.einsum('Eab,Eib->Eia', self.T_Fab, U_Eio[..., :-2]) # u2_Eie = np.einsum('ea,Eia->Eie', DELTA23_ab, u1_Eia) # return u2_Eie U1_Eio = np.copy(U_Eio).astype(np.float_) U1_Eio[..., :3, :3] = u1_Eia return U1_Eio def f2F(self, f_Eid): F0_Eia = np.concatenate( [f_Eid, np.zeros_like(f_Eid[...,:1])], axis=-1) return F0_Eia def xf2F(self, f_Eid): # F1_Eia = np.einsum('da,Eid->Eia', DELTA23_ab, f_Eid) print('f_Eid=', f_Eid) # F2_Eia = np.einsum('Eab,Eia->Eib', self.T_Fab, f_Eid) F2_Eia = np.einsum('Eab,Eia->Eib', self.T_Fab, f_Eid[..., :-2]) f1_Eid = np.copy(f_Eid).astype(np.float_) f1_Eid[..., :-2] = F2_Eia return f1_Eid def k2K(self, K_Eiejf): K0_Eicjf = np.concatenate([K_Eiejf, np.zeros_like(K_Eiejf[:,:,:1,:,:])], axis=2) K0_Eicjd = np.concatenate([K0_Eicjf, np.zeros_like(K0_Eicjf[:,:,:,:,:1])], axis=4) return K0_Eicjd def xk2K(self, K_Eiejf): # K1_Eiejf = np.einsum('ea,fb,Eiejf->Eiajb', DELTA23_ab, DELTA23_ab, K_Eiejf) # correct T_Eeafb = np.einsum('Eea,Efb->Eeafb', self.T_Fab, self.T_Fab) #K_Eab = np.einsum('Eeafb,ef->Eab', T_Eeafb, k_ef) # K2_Eiajb = np.einsum('Eeafb,Eiejf->Eiajb', T_Eeafb, K_Eiejf) K2_Eiajb = np.einsum('Eeafb,Eiejf->Eiajb', T_Eeafb, K_Eiejf[:,:,:-2,:,:-2]) K1_Eiejf = np.copy(K_Eiejf).astype(np.float_) K1_Eiejf[:,:,:-2,:,:-2] = K2_Eiajb #K2_Eicjd = np.einsum('Eca,Ebd,Eiajb->Eicjd', self.T_Fab, self.T_Fab, K1_Eicjd) #K2_Eicjd = np.einsum('Eac,Edb,Eiajb->Eicjd', self.T_Fab, self.T_Fab, K1_Eicjd) return K1_Eiejf I_CDij = tr.Property def _get_I_CDij(self): return self.mesh.I_CDij bc_J_F_xyz= tr.Property(depends_on='state_changed') @tr.cached_property def _get_bc_J_F_xyz(self): ix2 = int((self.mesh.n_phi_plus) / 2) F_I = self.I_CDij[ix2, :, 0, :].flatten() _, idx_remap = self.mesh.unique_node_map return idx_remap[F_I] bc_J_xyz = tr.Property(depends_on='state_changed') @tr.cached_property def _get_bc_J_xyz(self): I_M = self.I_CDij[(0, -1), :, (0, -1), :].flatten() _, idx_remap = self.mesh.unique_node_map J_M = idx_remap[I_M] return J_M bc_J_x = tr.Property(depends_on='state_changed') @tr.cached_property def _get_bc_J_x(self): I_M = self.I_CDij[:, (0, -1), :, (0, -1)].flatten() _, idx_remap = self.mesh.unique_node_map J_M = idx_remap[I_M] return J_M def setup_plot(self, pb): X_Ia = self.mesh.X_Ia.astype(np.float32) I_Fi = self.mesh.I_Fi.astype(np.uint32) X_Ma = X_Ia[self.bc_J_xyz] self.k3d_fixed_xyz = k3d.points(X_Ma) pb.plot_fig += self.k3d_fixed_xyz X_Ma = X_Ia[self.bc_J_x] self.k3d_fixed_x = k3d.points(X_Ma, color=0x22ffff) pb.plot_fig += self.k3d_fixed_x X_Ma = X_Ia[self.bc_J_F_xyz] self.k3d_load_z = k3d.points(X_Ma, color=0xff22ff) pb.plot_fig += self.k3d_load_z self.k3d_mesh = k3d.mesh(X_Ia, I_Fi, color=0x999999, side='double') pb.plot_fig += self.k3d_mesh def update_plot(self, pb): X_Ia = self.mesh.X_Ia.astype(np.float32) I_Fi = self.mesh.I_Fi.astype(np.uint32) self.k3d_fixed_xyz.positions = X_Ia[self.bc_J_xyz] self.k3d_fixed_x.positions = X_Ia[self.bc_J_x] self.k3d_load_z.positions = X_Ia[self.bc_J_F_xyz] mesh = self.k3d_mesh mesh.vertices = X_Ia mesh.indices = I_Fi def _get_du_dr_Fmra(self, U_o): dh_imr = self.fets.dh_imr dht_imr = self.fets.dht_imr _, v1_Fid, v2_Fid = self.v_vectors a = self.fets.a # Calculating du_dr U_o = np.arange(3 * 5) # TODO U_o comes from function arguments, this is just for testing nodes_num = self.mesh.X_Id.shape[0] U_Ie = np.reshape(U_o, (nodes_num, self.fets.n_nodal_dofs)) U_Fie = U_Ie[self.mesh.I_Fi] disp_U_Fia = U_Fie[..., :3] rot_U_Fib = U_Fie[..., 3:] du_dr1_Fmria = np.einsum('Fia, imr ->Fmria', disp_U_Fia, dh_imr) du_dr1_Fmra = np.sum(du_dr1_Fmria, axis=3) alpha_idx = 0 beta_idx = 1 alpha_Fi1 = rot_U_Fib[..., alpha_idx, np.newaxis] beta_Fi1 = rot_U_Fib[..., beta_idx, np.newaxis] v2_alpha_Fid = v2_Fid * alpha_Fi1 v1_beta_Fid = v1_Fid * beta_Fi1 v1_v2_dif_Fid = v1_beta_Fid - v2_alpha_Fid du_dr2_Fmria = np.einsum('Fia, imr ->Fmria', 0.5 * a * v1_v2_dif_Fid, dht_imr) du_dr2_Fmra = np.sum(du_dr2_Fmria, axis=3) du_dr_Fmra = du_dr1_Fmra + du_dr2_Fmra return du_dr_Fmra v_vectors = tr.Property(depends_on='+GEO') @tr.cached_property def _get_v_vectors(self): # See P. 472 FEM by Zienkiewicz (ISBN: 1856176339) # Calculating v_n (director vector) n_Fd = self.norm_F_normals el_nodes_num = 3 Vn_Fid = np.tile(n_Fd, (1, 1, el_nodes_num)).reshape(n_Fd.shape[0], n_Fd.shape[1], el_nodes_num) # Calculating v1 and v2 (vectors perpendicular to director vector) min_Fi1 = np.abs(Vn_Fid).argmin(axis=2) min_Fi1 = min_Fi1[..., np.newaxis] tmp_Fid = np.zeros_like(Vn_Fid, dtype=np.int_) tmp_Fid[..., :] = np.arange(3) min_mask_Fid = tmp_Fid == min_Fi1 e_x_min_Fid = min_mask_Fid * 1 V1_Fid = np.cross(e_x_min_Fid, Vn_Fid) V2_Fid = np.cross(Vn_Fid, V1_Fid) v1_Fid = self._normalize(V1_Fid) v2_Fid = self._normalize(V2_Fid) return Vn_Fid, v1_Fid, v2_Fid def _normalize(self, V_Fid): mag_n = np.sqrt(np.einsum('...i,...i', V_Fid, V_Fid)) v_Fid = V_Fid / mag_n[:, np.newaxis] return v_Fid dx_dr_Fmrd = tr.Property(depends_on='+GEO') @tr.cached_property def _get_dx_dr_Fmrd(self): Vn_Fid, _, _ = self.v_vectors dh_imr = self.fets.dh_imr dht_imr = self.fets.dht_imr # X_Fid = self.X_Id[self.mesh.I_Fi] # X_Fid = self.X_Id[self.F_N] # get coords transformed to local X_Fid = self.x_Eia a = self.fets.a # thickness (TODO, make it available as input) dx_dr1_Fmrid = np.einsum('Fid, imr -> Fmrid', X_Fid, dh_imr) dx_dr2_Fmrid = np.einsum('Fid, imr -> Fmrid', 0.5 * a * Vn_Fid, dht_imr) dx_dr1_Fmrd = np.sum(dx_dr1_Fmrid, axis=3) dx_dr2_Fmrd = np.sum(dx_dr2_Fmrid, axis=3) dx_dr_Fmrd = dx_dr1_Fmrd + dx_dr2_Fmrd return dx_dr_Fmrd B_Emiabo = tr.Property(depends_on='+GEO') @tr.cached_property def _get_B_Emiabo(self): delta35_co = np.zeros((3, 5), dtype='f') delta35_co[(0, 1, 2), (0, 1, 2)] = 1 delta25_vo = np.zeros((2, 5), dtype='f') delta25_vo[(0, 1), (3, 4)] = 1 _, v1_Fid, v2_Fid = self.v_vectors V_Ficv = np.zeros((*v2_Fid.shape, 2), dtype='f') V_Ficv[..., 0] = v1_Fid V_Ficv[..., 1] = v2_Fid # Thickness a a = self.fets.a dN_imr = self.fets.dh_imr dNt_imr = self.fets.dht_imr delta = np.identity(3) Diff1_abcd = 0.5 * ( np.einsum('ac,bd->abcd', delta, delta) + np.einsum('ad,bc->abcd', delta, delta) ) J_Fmrd = self.dx_dr_Fmrd inv_J_Fmrd = np.linalg.inv(J_Fmrd) det_J_Fm = np.linalg.det(J_Fmrd) B1_Emiabo = np.einsum('abcd, imr, co, Emrd -> Emiabo', Diff1_abcd, dN_imr, delta35_co, inv_J_Fmrd) B2_Emiabo = np.einsum('abcd, imr, Ficv, vo, Emrd -> Emiabo', Diff1_abcd, dNt_imr, 0.5 * a * V_Ficv, delta25_vo, inv_J_Fmrd) B_Emiabo = B1_Emiabo + B2_Emiabo # B_Emiabo = np.flip(B_Emiabo, 2) return B_Emiabo, det_J_Fm def _get_B_Empf(self): B_Emiabo, _ = self.B_Emiabo # Mapping ab to p (3x3 -> 5) B_Emipo = B_Emiabo[:, :, :, (0, 1, 0, 1, 2), (0, 1, 1, 2, 0), :] E, m, i, p, o = B_Emipo.shape B_Empio = np.einsum('Emipo->Empio', B_Emipo) B_Empf = B_Empio.reshape((E, m, p, 3 * o)) # p: index with max value 5 # f: index with max value 15 return B_Empf def map_U_to_field(self, U_o): # print('map_U_to_field') # # For testing with one element: # U_io = np.array([[1, 0, 0, 0, 0], # [0, 0, 0.5, 0, 0], # [0, 1, 0, 0, 0]], dtype=np.float_) print('U_o:', U_o) # TODO: check if the transformation caused by following line is needed U_Eio = U_o[self.o_Eia] # print('U_Eio,', U_Eio) # U_Eio = U_o.reshape((-1, self.fets.n_nodal_dofs))[self.mesh.I_Fi] # U_Eio = U_o.reshape((-1, self.fets.n_nodal_dofs))[self.F_N] # transform to local U_Eio = self.xU2u(U_Eio) print('U_Eio,', U_Eio) B_Emiabo, _ = self.B_Emiabo eps_Emab = np.einsum('Emiabo, Eio -> Emab', B_Emiabo, U_Eio) eps_Emp = eps_Emab[:, :, (0, 1, 0, 1, 0), (0, 1, 1, 2, 2)] return eps_Emp def map_field_to_F(self, sig_Ems): # print('map_field_to_F') print('sig_Es', sig_Ems) _, det_J_Fm = self.B_Emiabo B_Empf = self._get_B_Empf() f_Emf = self.integ_factor * np.einsum('m, Emsf, Ems, Em -> Emf', self.fets.w_m, B_Empf, sig_Ems, det_J_Fm) f_Ef = np.sum(f_Emf, axis=1) # o_Ei = self.o_Eia.reshape(-1, 3 * 5) # print('o_Ei:', o_Ei) # # o_Ei = self.o_Ia[self.mesh.I_Fi].reshape(-1, 3 * self.fets.n_nodal_dofs) # # o_Ei = self.o_Ia[self.F_N].reshape(-1, 3 * self.fets.n_nodal_dofs) # return o_Ei.flatten(), f_Ef.flatten() f_Eic = f_Ef.reshape(-1, 3, 5) # coordinate transform to global f_Eic = self.xf2F(f_Eic) _, n_i, n_c = f_Eic.shape f_Ei = f_Eic.reshape(-1, n_i * n_c) o_E = self.o_Eia.reshape(-1, n_i * n_c) return o_E.flatten(), f_Ei.flatten() def map_field_to_K(self, D_Est): # print('map_field_to_K') w_m = self.fets.w_m # Gauss points weights B_Emiabo, det_J_Fm = self.B_Emiabo B_Empf = self._get_B_Empf() k2_Emop = self.integ_factor * np.einsum('m, Empf, Ept, Emtq, Em -> Emfq', w_m, B_Empf, D_Est, B_Empf, det_J_Fm) k2_Eop = np.sum(k2_Emop, axis=1) # o_Ei = self.o_Eia.reshape(-1, 3 * 5) # print('o_Ei:', o_Ei) # # # o_Ei = self.o_Ia[self.mesh.I_Fi].reshape(-1, 3 * self.fets.n_nodal_dofs) # # o_Ei = self.o_Ia[self.F_N].reshape(-1, 3 * self.fets.n_nodal_dofs) # # return SysMtxArray(mtx_arr=k2_Eop, dof_map_arr=o_Ei) K_Eiejf = k2_Eop.reshape(-1, 3, 5, 3, 5) K_Eicjd = self.xk2K(K_Eiejf) _, n_i, n_c, n_j, n_d = K_Eicjd.shape K_Eij = K_Eicjd.reshape(-1, n_i * n_c, n_j * n_d) o_Ei = self.o_Eia.reshape(-1, n_i * n_c) return SysMtxArray(mtx_arr=K_Eij, dof_map_arr=o_Ei) # O_Eo = tr.Property(tr.Array, depends_on='X, L, F') # @tr.cached_property # def _get_O_Eo(self): # return self.o_Ia[self.F_N].reshape(-1, 3 * self.fets.n_nodal_dofs) # ========================================================================= # Property operators for initial configuration # ========================================================================= F0_normals = tr.Property(tr.Array, depends_on='X, L, F') r'''Normal facet vectors. ''' @tr.cached_property def _get_F0_normals(self): x_F = self.x_0[self.F] N_deta_ip = self.Na_deta r_deta = np.einsum('ajK,IKi->Iaij', N_deta_ip, x_F) Fa_normals = np.einsum('Iai,Iaj,ijk->Iak', r_deta[..., 0], r_deta[..., 1], EPS) return np.sum(Fa_normals, axis=1) sign_normals = tr.Property(tr.Array, depends_on='X,L,F') r'''Orientation of the normal in the initial state. This array is used to switch the normal vectors of the faces to be oriented in the positive sense of the z-axis. ''' @tr.cached_property def _get_sign_normals(self): return np.sign(self.F0_normals[:, 2]) F_N = tr.Property(tr.Array, depends_on='X,L,F') r'''Counter-clockwise enumeration. ''' @tr.cached_property def _get_F_N(self): # TODO: following test may be not valid in 3D shells case! other condition is to be taken turn_facets = np.where(self.sign_normals < 0) F_N = np.copy(self.F) F_N[turn_facets, :] = self.F[turn_facets, ::-1] return F_N F_normals = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normals of the facets. ''' @tr.cached_property def _get_F_normals(self): n = self.Fa_normals return np.sum(n, axis=1) F_normals_0 = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normals of the facets. ''' @tr.cached_property def _get_F_normals_0(self): n = self.Fa_normals_0 return np.sum(n, axis=1) norm_F_normals = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normed normals of the facets. ''' @tr.cached_property def _get_norm_F_normals(self): n = self.F_normals mag_n = np.sqrt(np.einsum('...i,...i', n, n)) return n / mag_n[:, np.newaxis] norm_F_normals_0 = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normed normals of the facets. ''' @tr.cached_property def _get_norm_F_normals_0(self): n = self.F_normals_0 mag_n = np.sqrt(np.einsum('...i,...i', n, n)) return n / mag_n[:, np.newaxis] F_normals_du = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normals of the facets. ''' @tr.cached_property def _get_F_normals_du(self): n_du = self.Fa_normals_du return np.sum(n_du, axis=1) F_area = tr.Property(tr.Array, depends_on=INPUT) r'''Get the surface area of the facets. ''' @tr.cached_property def _get_F_area(self): a = self.Fa_area A = np.einsum('a,Ia->I', self.eta_w, a) return A # ========================================================================= # Potential energy # ========================================================================= F_V = tr.Property(tr.Array, depends_on=INPUT) r'''Get the total potential energy of gravity for each facet ''' @tr.cached_property def _get_F_V(self): eta_w = self.eta_w a = self.Fa_area ra = self.Fa_r F_V = np.einsum('a,Ia,Ia->I', eta_w, ra[..., 2], a) return F_V F_V_du = tr.Property(tr.Array, depends_on=INPUT) r'''Get the derivative of total potential energy of gravity for each facet with respect to each node and displacement component [FIi] ''' @tr.cached_property def _get_F_V_du(self): r = self.Fa_r a = self.Fa_area a_dx = self.Fa_area_du r3_a_dx = np.einsum('Ia,IaJj->IaJj', r[..., 2], a_dx) N_eta_ip = self.Na r3_dx = np.einsum('aK,KJ,j->aJj', N_eta_ip, DELTA, DELTA[2, :]) a_r3_dx = np.einsum('Ia,aJj->IaJj', a, r3_dx) F_V_du = np.einsum('a,IaJj->IJj', self.eta_w, (a_r3_dx + r3_a_dx)) return F_V_du # ========================================================================= # Line vectors # ========================================================================= F_L_vectors_0 = tr.Property(tr.Array, depends_on=INPUT) r'''Get the cycled line vectors around the facet The cycle is closed - the first and last vector are identical. .. math:: v_{pld} \;\mathrm{where} \; p\in\mathcal{F}, l\in (0,1,2), d\in (0,1,2) with the indices :math:`p,l,d` representing the facet, line vector around the facet and and vector component, respectively. ''' @tr.cached_property def _get_F_L_vectors_0(self): F_N = self.F_N # F_N is cycled counter clockwise return self.x_0[F_N[:, (1, 2, 0)]] - self.x_0[F_N[:, (0, 1, 2)]] F_L_vectors = tr.Property(tr.Array, depends_on=INPUT) r'''Get the cycled line vectors around the facet The cycle is closed - the first and last vector are identical. .. math:: v_{pld} \;\mathrm{where} \; p\in\mathcal{F}, l\in (0,1,2), d\in (0,1,2) with the indices :math:`p,l,d` representing the facet, line vector around the facet and and vector component, respectively. ''' @tr.cached_property def _get_F_L_vectors(self): F_N = self.F_N # F_N is cycled counter clockwise return self.x[F_N[:, (1, 2, 0)]] - self.x[F_N[:, (0, 1, 2)]] F_L_vectors_du = tr.Property(tr.Array, depends_on=INPUT) r'''Get the derivatives of the line vectors around the facets. .. math:: \pard{v_{pld}}{x_{Ie}} \; \mathrm{where} \; p \in \mathcal{F}, \in (0,1,2), d\in (0,1,2), I\in \mathcal{N}, e \in (0,1,3) with the indices :math:`p,l,d,I,e` representing the facet, line vector around the facet and and vector component, node vector and and its component index, respectively. This array works essentially as an index function delivering -1 for the components of the first node in each dimension and +1 for the components of the second node in each dimension. For a facet :math:`p` with lines :math:`l` and component :math:`d` return the derivatives with respect to the displacement of the node :math:`I` in the direction :math:`e`. .. math:: \bm{a}_1 = \bm{x}_2 - \bm{x}_1 \\ \bm{a}_2 = \bm{x}_3 - \bm{x}_2 \\ \bm{a}_3 = \bm{x}_1 - \bm{x}_3 The corresponding derivatives are then .. math:: \pard{\bm{a}_1}{\bm{u}_1} = -1, \;\;\; \pard{\bm{a}_1}{\bm{u}_2} = 1 \\ \pard{\bm{a}_2}{\bm{u}_2} = -1, \;\;\; \pard{\bm{a}_2}{\bm{u}_3} = 1 \\ \pard{\bm{a}_3}{\bm{u}_3} = -1, \;\;\; \pard{\bm{a}_3}{\bm{u}_1} = 1 \\ ''' def _get_F_L_vectors_du(self): return self.L_vectors_du[self.F_L] F_L_vectors_dul = tr.Property(tr.Array, depends_on=INPUT) def _get_F_L_vectors_dul(self): return self.L_vectors_dul[self.F_L] norm_F_L_vectors = tr.Property(tr.Array, depends_on=INPUT) r'''Get the cycled line vectors around the facet The cycle is closed - the first and last vector are identical. ''' @tr.cached_property def _get_norm_F_L_vectors(self): v = self.F_L_vectors mag_v = np.sqrt(np.einsum('...i,...i', v, v)) return v / mag_v[..., np.newaxis] norm_F_L_vectors_du = tr.Property(tr.Array, depends_on=INPUT) '''Get the derivatives of cycled line vectors around the facet ''' @tr.cached_property def _get_norm_F_L_vectors_du(self): v = self.F_L_vectors v_du = self.F_L_vectors_du # @UnusedVariable mag_v = np.einsum('...i,...i', v, v) # @UnusedVariable # @todo: finish the chain rule raise NotImplemented # ========================================================================= # Orthonormal basis of each facet. # ========================================================================= F_L_bases = tr.Property(tr.Array, depends_on=INPUT) r'''Line bases around a facet. ''' @tr.cached_property def _get_F_L_bases(self): l = self.norm_F_L_vectors n = self.norm_F_normals lxn = np.einsum('...li,...j,...kij->...lk', l, n, EPS) n_ = n[:, np.newaxis, :] * np.ones((1, 3, 1), dtype='float_') T = np.concatenate([l[:, :, np.newaxis, :], -lxn[:, :, np.newaxis, :], n_[:, :, np.newaxis, :]], axis=2) return T F_L_bases_du = tr.Property(tr.Array, depends_on=INPUT) r'''Derivatives of the line bases around a facet. ''' @tr.cached_property def _get_F_L_bases_du(self): '''Derivatives of line bases''' raise NotImplemented # ========================================================================= # Sector angles # ========================================================================= F_theta = tr.Property(tr.Array, depends_on=INPUT) '''Get the sector angles :math:`\theta` within a facet. ''' @tr.cached_property def _get_F_theta(self): v = self.F_L_vectors a = -v[:, (2, 0, 1), :] b = v[:, (0, 1, 2), :] return get_theta(a, b) F_theta_du = tr.Property(tr.Array, depends_on=INPUT) r'''Get the derivatives of sector angles :math:`\theta` within a facet. ''' @tr.cached_property def _get_F_theta_du(self): v = self.F_L_vectors v_du = self.F_L_vectors_du a = -v[:, (2, 0, 1), :] b = v[:, (0, 1, 2), :] a_du = -v_du[:, (2, 0, 1), ...] b_du = v_du[:, (0, 1, 2), ...] return get_theta_du(a, a_du, b, b_du) Fa_normals_du = tr.Property '''Get the derivatives of the normals with respect to the node displacements. ''' def _get_Fa_normals_du(self): x_F = self.x[self.F_N] N_deta_ip = self.Na_deta NN_delta_eps_x1 = np.einsum('aK,aL,KJ,dli,ILl->IaiJd', N_deta_ip[:, 0, :], N_deta_ip[:, 1, :], DELTA, EPS, x_F) NN_delta_eps_x2 = np.einsum('aK,aL,LJ,kdi,IKk->IaiJd', N_deta_ip[:, 0, :], N_deta_ip[:, 1, :], DELTA, EPS, x_F) n_du = NN_delta_eps_x1 + NN_delta_eps_x2 return n_du Fa_area_du = tr.Property '''Get the derivatives of the facet area with respect to node displacements. ''' def _get_Fa_area_du(self): a = self.Fa_area n = self.Fa_normals n_du = self.Fa_normals_du a_du = np.einsum('Ia,Iak,IakJd->IaJd', 1 / a, n, n_du) return a_du Fa_normals = tr.Property '''Get normals of the facets. ''' def _get_Fa_normals(self): # TODO, check this change # x_F = self.x[self.mesh.I_Fi] x_F = self.x[self.F_N] N_deta_ip = self.Na_deta r_deta = np.einsum('ajK,IKi->Iaij', N_deta_ip, x_F) return np.einsum('Iai,Iaj,ijk->Iak', r_deta[..., 0], r_deta[..., 1], EPS) Fa_normals_0 = tr.Property '''Get normals of the facets. ''' def _get_Fa_normals_0(self): x_F = self.x_0[self.F_N] N_deta_ip = self.Na_deta r_deta = np.einsum('ajK,IKi->Iaij', N_deta_ip, x_F) return np.einsum('Iai,Iaj,ijk->Iak', r_deta[..., 0], r_deta[..., 1], EPS) Fa_area = tr.Property '''Get the surface area of the facets. ''' def _get_Fa_area(self): n = self.Fa_normals a = np.sqrt(np.einsum('Iai,Iai->Ia', n, n)) return a Fa_r = tr.Property '''Get the reference vector to integrations point in each facet. ''' def _get_Fa_r(self): x_F = self.x[self.F_N] N_eta_ip = self.Na r = np.einsum('aK,IKi->Iai', N_eta_ip, x_F) return r
class MKappa(InteractiveModel, InjectSymbExpr): """Class returning the moment curvature relationship.""" name = 'Moment-Curvature' symb_class = MKappaSymbolic cs_design = Instance(CrossSectionDesign, ()) tree = ['cs_design'] # Use PrototypedFrom only when the prototyped object is a class # (The prototyped attribute behaves similarly # to a delegated attribute, until it is explicitly # changed; from that point forward, the prototyped attribute # changes independently from its prototype.) # (it's kind of like tr.DelegatesTo('cs_design.cross_section_shape')) cross_section_shape = tr.DelegatesTo('cs_design') cross_section_shape_ = tr.DelegatesTo('cs_design') cross_section_layout = tr.DelegatesTo('cs_design') matrix_ = tr.DelegatesTo('cs_design') # Geometry H = tr.DelegatesTo('cross_section_shape_') DEPSTR = 'state_changed' n_m = Int( 100, DSC=True, desc= 'Number of discretization points along the height of the cross-section' ) # @todo: fix the dependency - `H` should be replaced by _GEO z_m = tr.Property(depends_on=DEPSTR) @tr.cached_property def _get_z_m(self): return np.linspace(0, self.H, self.n_m) low_kappa = Float(0.0, BC=True, GEO=True) high_kappa = Float(0.00002, BC=True, GEO=True) n_kappa = Int(100, BC=True) step_kappa = tr.Property(Float, depends_on='low_kappa, high_kappa') @tr.cached_property def _get_step_kappa(self): return float((self.high_kappa - self.low_kappa) / self.n_kappa) kappa_slider = Float(0.0000001) ipw_view = View( Item( 'low_kappa', latex=r'\text{Low}~\kappa'), #, editor=FloatEditor(step=0.00001)), Item('high_kappa', latex=r'\text{High}~\kappa' ), # , editor=FloatEditor(step=0.00001)), Item('n_kappa', latex='n_{\kappa}'), Item('plot_strain'), Item('solve_for_eps_bot_pointwise'), Item('n_m', latex='n_m'), Item('kappa_slider', latex='\kappa', readonly=True), # editor=FloatRangeEditor(low_name='low_kappa', # high_name='high_kappa', # n_steps_name='n_kappa') # ), time_editor=HistoryEditor( var='kappa_slider', min_var='low_kappa', max_var='high_kappa', ), ) idx = tr.Property(depends_on='kappa_slider') apply_material_safety_factors = tr.Bool(False) @tr.cached_property def _get_idx(self): ks = self.kappa_slider idx = np.argmax(ks <= self.kappa_t) return idx kappa_t = tr.Property(tr.Array(np.float_), depends_on=DEPSTR) '''Curvature values for which the bending moment must be found ''' @tr.cached_property def _get_kappa_t(self): return np.linspace(self.low_kappa, self.high_kappa, self.n_kappa) z_j = tr.Property def _get_z_j(self): return self.cross_section_layout.z_j A_j = tr.Property def _get_A_j(self): return self.cross_section_layout.A_j # Normal force in steel (tension and compression) def get_N_s_tj(self, kappa_t, eps_bot_t): # get the strain at the height of the reinforcement eps_z_tj = self.symb.get_eps_z(kappa_t[:, np.newaxis], eps_bot_t[:, np.newaxis], self.z_j[np.newaxis, :]) # Get the crack bridging force in each reinforcement layer # given the corresponding crack-bridge law. N_s_tj = self.cross_section_layout.get_N_tj(eps_z_tj) return N_s_tj # TODO - [RC] avoid repeated evaluations of stress profile in # N and M calculations for the same inputs as it # is the case now. def get_sig_c_z(self, kappa_t, eps_bot_t, z_tm): """Get the stress profile over the height""" eps_z = self.symb.get_eps_z(kappa_t[:, np.newaxis], eps_bot_t[:, np.newaxis], z_tm) sig_c_z = self.matrix_.get_sig(eps_z) return sig_c_z # Normal force in concrete (tension and compression) def get_N_c_t(self, kappa_t, eps_bot_t): z_tm = self.z_m[np.newaxis, :] b_z_m = self.cross_section_shape_.get_b(z_tm) N_z_tm2 = b_z_m * self.get_sig_c_z(kappa_t, eps_bot_t, z_tm) return np.trapz(N_z_tm2, x=z_tm, axis=-1) def get_N_t(self, kappa_t, eps_bot_t): N_s_t = np.sum(self.get_N_s_tj(kappa_t, eps_bot_t), axis=-1) N_c_t = self.get_N_c_t(kappa_t, eps_bot_t) return N_c_t + N_s_t # SOLVER: Get eps_bot to render zero force # num_of_trials = tr.Int(30) eps_bot_t = tr.Property(depends_on=DEPSTR) r'''Resolve the tensile strain to get zero normal force for the prescribed curvature''' # @tr.cached_property # def _get_eps_bot_t(self): # initial_step = (self.high_kappa - self.low_kappa) / self.num_of_trials # for i in range(self.num_of_trials): # print('Solution started...') # res = root(lambda eps_bot_t: self.get_N_t(self.kappa_t, eps_bot_t), # 0.0000001 + np.zeros_like(self.kappa_t), tol=1e-6) # if res.success: # print('success high_kappa: ', self.high_kappa) # if i == 0: # print('Note: high_kappa success from 1st try! selecting a higher value for high_kappa may produce ' # 'a more reliable result!') # return res.x # else: # print('failed high_kappa: ', self.high_kappa) # self.high_kappa -= initial_step # self.kappa_t = np.linspace(self.low_kappa, self.high_kappa, self.n_kappa) # # print('No solution', res.message) # return res.x # @tr.cached_property # def _get_eps_bot_t(self): # res = root(lambda eps_bot_t: self.get_N_t(self.kappa_t, eps_bot_t), # 0.0000001 + np.zeros_like(self.kappa_t), tol=1e-6) # if not res.success: # raise SolutionNotFoundError('No solution', res.message) # return res.x solve_for_eps_bot_pointwise = Bool(True, BC=True, GEO=True) @tr.cached_property def _get_eps_bot_t(self): if self.solve_for_eps_bot_pointwise: """ INFO: Instability in eps_bot solutions was caused by unsuitable init_guess value causing a convergence to non-desired solutions. Solving the whole kappa_t array improved the init_guess after each calculated value, however, instability still there. The best results were obtained by taking the last solution as the init_guess for the next solution like in the following.. """ # One by one solution for kappa values eps_bot_sol_for_pos_kappa = self._get_eps_bot_piecewise_sol( kappa_pos=True) eps_bot_sol_for_neg_kappa = self._get_eps_bot_piecewise_sol( kappa_pos=False) res = np.concatenate( [eps_bot_sol_for_neg_kappa, eps_bot_sol_for_pos_kappa]) return res else: # Array solution for the whole kappa_t res = root(lambda eps_bot_t: self.get_N_t(self.kappa_t, eps_bot_t), 0.0000001 + np.zeros_like(self.kappa_t), tol=1e-6) if not res.success: print('No solution', res.message) return res.x def _get_eps_bot_piecewise_sol(self, kappa_pos=True): if kappa_pos: kappas = self.kappa_t[np.where(self.kappa_t >= 0)] else: kappas = self.kappa_t[np.where(self.kappa_t < 0)] res = [] if kappa_pos: init_guess = 0.00001 kappa_loop_list = kappas else: init_guess = -0.00001 kappa_loop_list = reversed(kappas) for kappa in kappa_loop_list: sol = root( lambda eps_bot: self.get_N_t(np.array([kappa]), eps_bot), np.array([init_guess]), tol=1e-6).x[0] # This condition is to avoid having init_guess~0 which causes non-convergence if abs(sol) > 1e-5: init_guess = sol res.append(sol) if kappa_pos: return res else: return list(reversed(res)) # POSTPROCESSING kappa_cr = tr.Property(depends_on=DEPSTR) '''Curvature at which a critical strain is attained at the eps_bot''' @tr.cached_property def _get_kappa_cr(self): res = root(lambda kappa: self.get_N_t(kappa, self.eps_cr), 0.0000001 + np.zeros_like(self.eps_cr), tol=1e-10) if not res.success: print('No kappa_cr solution (for plot_norm() function)', res.message) return res.x M_s_t = tr.Property(depends_on=DEPSTR) '''Bending moment (steel) ''' @tr.cached_property def _get_M_s_t(self): if len(self.z_j) == 0: return np.zeros_like(self.kappa_t) eps_z_tj = self.symb.get_eps_z(self.kappa_t[:, np.newaxis], self.eps_bot_t[:, np.newaxis], self.z_j[np.newaxis, :]) # Get the crack bridging force in each reinforcement layer # given the corresponding crack-bridge law. N_tj = self.cross_section_layout.get_N_tj(eps_z_tj) return -np.einsum('tj,j->t', N_tj, self.z_j) M_c_t = tr.Property(depends_on=DEPSTR) '''Bending moment (concrete) ''' @tr.cached_property def _get_M_c_t(self): z_tm = self.z_m[np.newaxis, :] b_z_m = self.cross_section_shape_.get_b(z_tm) N_z_tm2 = b_z_m * self.get_sig_c_z(self.kappa_t, self.eps_bot_t, z_tm) return -np.trapz(N_z_tm2 * z_tm, x=z_tm, axis=-1) M_t = tr.Property(depends_on=DEPSTR) '''Bending moment ''' @tr.cached_property def _get_M_t(self): # print('M - k recalculated') eta_factor = 1. return eta_factor * (self.M_c_t + self.M_s_t) # @tr.cached_property # def _get_M_t(self): # initial_step = (self.high_kappa - self.low_kappa) / self.num_of_trials # for i in range(self.num_of_trials): # try: # M_t = self.M_c_t + self.M_s_t # except SolutionNotFoundError: # print('failed high_kappa: ', self.high_kappa) # self.high_kappa -= initial_step # else: # # This will run when no exception has been received # print('success high_kappa: ', self.high_kappa) # if i == 0: # print('Note: high_kappa success from 1st try! selecting a higher value for high_kappa may produce ' # 'a more reliable result!') # return M_t # print('No solution has been found!') # return M_t N_s_tj = tr.Property(depends_on=DEPSTR) '''Normal forces (steel) ''' @tr.cached_property def _get_N_s_tj(self): return self.get_N_s_tj(self.kappa_t, self.eps_bot_t) eps_tm = tr.Property(depends_on=DEPSTR) '''strain profiles ''' @tr.cached_property def _get_eps_tm(self): return self.symb.get_eps_z(self.kappa_t[:, np.newaxis], self.eps_bot_t[:, np.newaxis], self.z_m[np.newaxis, :]) sig_tm = tr.Property(depends_on=DEPSTR) '''strain profiles ''' @tr.cached_property def _get_sig_tm(self): return self.get_sig_c_z(self.kappa_t, self.eps_bot_t, self.z_m[np.newaxis, :]) M_norm = tr.Property(depends_on=DEPSTR) ''' ''' @tr.cached_property def _get_M_norm(self): # Section modulus @TODO optimize W for var b W = (self.b * self.H**2) / 6 sig_cr = self.E_ct * self.eps_cr return W * sig_cr kappa_norm = tr.Property() def _get_kappa_norm(self): return self.kappa_cr inv_M_kappa = tr.Property(depends_on=DEPSTR) '''Return the inverted data points ''' @tr.cached_property def _get_inv_M_kappa(self): try: """cut off the descending tails""" M_t = self.M_t I_max = np.argmax(M_t) I_min = np.argmin(M_t) M_I = np.copy(M_t[I_min:I_max + 1]) kappa_I = np.copy(self.kappa_t[I_min:I_max + 1]) # find the index corresponding to zero kappa idx = np.argmax(0 <= kappa_I) # and modify the values such that the # Values of moment are non-descending M_plus = M_I[idx:] M_diff = M_plus[:, np.newaxis] - M_plus[np.newaxis, :] n_ij = len(M_plus) ij = np.mgrid[0:n_ij:1, 0:n_ij:1] M_diff[np.where(ij[1] >= ij[0])] = 0 i_x = np.argmin(M_diff, axis=1) M_I[idx:] = M_plus[i_x] return M_I, kappa_I except ValueError: print( 'M inverse has not succeeded, the M-Kappa solution may have failed due to ' 'a wrong kappa range or not suitable material law!') return np.array([0]), np.array([0]) def get_kappa_M(self, M): M_I, kappa_I = self.inv_M_kappa return np.interp(M, M_I, kappa_I) def plot_norm(self, ax1, ax2): idx = self.idx ax1.plot(self.kappa_t / self.kappa_norm, self.M_t / self.M_norm) ax1.plot(self.kappa_t[idx] / self.kappa_norm, self.M_t[idx] / self.M_norm, marker='o') ax2.barh(self.z_j, self.N_s_tj[idx, :], height=2, color='red', align='center') # ax2.fill_between(eps_z_arr[idx,:], z_arr, 0, alpha=0.1); ax3 = ax2.twiny() # ax3.plot(self.eps_tm[idx, :], self.z_m, color='k', linewidth=0.8) ax3.plot(self.sig_tm[idx, :], self.z_m) ax3.axvline(0, linewidth=0.8, color='k') ax3.fill_betweenx(self.z_m, self.sig_tm[idx, :], 0, alpha=0.1) mpl_align_xaxis(ax2, ax3) M_scale = Float(1e+6) plot_strain = Bool(False) def plot(self, ax1, ax2, ax3): self.plot_mk_and_stress_profile(ax1, ax2) if self.plot_strain: self.plot_strain_profile(ax3) else: self.plot_mk_inv(ax3) @staticmethod def subplots(fig): ax1, ax2, ax3 = fig.subplots(1, 3) return ax1, ax2, ax3 def update_plot(self, axes): self.plot(*axes) def plot_mk_inv(self, ax3): try: M, kappa = self.inv_M_kappa ax3.plot(M / self.M_scale, kappa) except ValueError: print( 'M inverse has not succeeded, the M-Kappa solution may have failed due to a wrong kappa range!' ) ax3.set_xlabel('Moment [kNm]') ax3.set_ylabel('Curvature[mm$^{-1}$]') def plot_mk_and_stress_profile(self, ax1, ax2): self.plot_mk(ax1) idx = self.idx ax1.plot(self.kappa_t[idx], self.M_t[idx] / self.M_scale, color='orange', marker='o') if len(self.z_j): ax2.barh(self.z_j, self.N_s_tj[idx, :] / self.A_j, height=4, color='red', align='center') ax2.set_ylabel('z [mm]') ax2.set_xlabel('$\sigma_r$ [MPa]') ax22 = ax2.twiny() ax22.set_xlabel('$\sigma_c$ [MPa]') ax22.plot(self.sig_tm[idx, :], self.z_m) ax22.axvline(0, linewidth=0.8, color='k') ax22.fill_betweenx(self.z_m, self.sig_tm[idx, :], 0, alpha=0.1) mpl_align_xaxis(ax2, ax22) def plot_mk(self, ax1): ax1.plot(self.kappa_t, self.M_t / self.M_scale, label='M-K') ax1.set_ylabel('Moment [kNm]') ax1.set_xlabel('Curvature [mm$^{-1}$]') ax1.legend() def plot_strain_profile(self, ax): ax.set_ylabel('z [mm]') ax.set_xlabel(r'$\varepsilon$ [-]') ax.plot(self.eps_tm[self.idx, :], self.z_m) ax.axvline(0, linewidth=0.8, color='k') ax.fill_betweenx(self.z_m, self.eps_tm[self.idx, :], 0, alpha=0.1) def get_mk(self): return self.M_t / self.M_scale, self.kappa_t
class ShmTrackingInterface(T.HasStrictTraits): dwi_images = T.DelegatesTo('all_inputs') all_inputs = T.Instance(InputData, args=()) min_signal = T.DelegatesTo('all_inputs') seed_roi = nifti_file seed_density = T.Array(dtype='int', shape=(3, ), value=[1, 1, 1]) smoothing_kernel_type = T.Enum(None, all_kernels.keys()) smoothing_kernel = T.Instance(T.HasTraits) @T.on_trait_change('smoothing_kernel_type') def set_smoothing_kernel(self): if self.smoothing_kernel_type is not None: kernel_factory = all_kernels[self.smoothing_kernel_type] self.smoothing_kernel = kernel_factory() else: self.smoothing_kernel = None interpolator = T.Enum('NearestNeighbor', all_interpolators.keys()) model_type = T.Enum('SlowAdcOpdf', all_shmodels.keys()) sh_order = T.Int(4) Lambda = T.Float(0, desc="Smoothing on the odf") sphere_coverage = T.Int(5) min_peak_spacing = T.Range(0., 1, np.sqrt(.5), desc="as a dot product") min_relative_peak = T.Range(0., 1, .25) probabilistic = T.Bool(False, label='Probabilistic (Residual Bootstrap)') bootstrap_input = T.Bool(False) bootstrap_vector = T.Array(dtype='int', value=[]) # integrator = Enum('Boundry', all_integrators.keys()) seed_largest_peak = T.Bool(False, desc="Ignore sub-peaks and start follow " "the largest peak at each seed") start_direction = T.Array(dtype='float', shape=(3, ), value=[0, 0, 1], desc="Prefered direction from seeds when " "multiple directions are available. " "(Mostly) doesn't matter when 'seed " "largest peak' and 'track two directions' " "are both True", label="Start direction (RAS)") track_two_directions = T.Bool(False) fa_threshold = T.Float(1.0) max_turn_angle = T.Range(0., 90, 0) stop_on_target = T.Bool(False) targets = T.List(nifti_file, []) # will be set later voxel_size = T.Array(dtype='float', shape=(3, )) affine = T.Array(dtype='float', shape=(4, 4)) shape = T.Tuple((0, 0, 0)) # set for io save_streamlines_to = T.File('') save_counts_to = nifti_file # io methods def save_streamlines(self, streamlines, save_streamlines_to): trk_hdr = empty_header() voxel_order = orientation_to_string(nib.io_orientation(self.affine)) trk_hdr['voxel_order'] = voxel_order trk_hdr['voxel_size'] = self.voxel_size trk_hdr['vox_to_ras'] = self.affine trk_hdr['dim'] = self.shape trk_tracks = ((ii, None, None) for ii in streamlines) write(save_streamlines_to, trk_tracks, trk_hdr) pickle.dump(self, open(save_streamlines_to + '.p', 'wb')) def save_counts(self, streamlines, save_counts_to): counts = density_map(streamlines, self.shape, self.voxel_size) if counts.max() < 2**15: counts = counts.astype('int16') nib.save(nib.Nifti1Image(counts, self.affine), save_counts_to) # tracking methods def track_shm(self, debug=False): if self.sphere_coverage > 7 or self.sphere_coverage < 1: raise ValueError("sphere coverage must be between 1 and 7") verts, edges, faces = create_half_unit_sphere(self.sphere_coverage) verts, pot = disperse_charges(verts, 10, .3) data, voxel_size, affine, fa, bvec, bval = self.all_inputs.read_data() self.voxel_size = voxel_size self.affine = affine self.shape = fa.shape model_type = all_shmodels[self.model_type] model = model_type(self.sh_order, bval, bvec, self.Lambda) model.set_sampling_points(verts, edges) data = np.asarray(data, dtype='float', order='C') if self.smoothing_kernel is not None: kernel = self.smoothing_kernel.get_kernel() convolve(data, kernel, out=data) normalize_data(data, bval, self.min_signal, out=data) dmin = data.min() data = data[..., lazy_index(bval > 0)] if self.bootstrap_input: if self.bootstrap_vector.size == 0: n = data.shape[-1] self.bootstrap_vector = np.random.randint(n, size=n) H = hat(model.B) R = lcr_matrix(H) data = bootstrap_data_array(data, H, R, self.bootstrap_vector) data.clip(dmin, out=data) mask = fa > self.fa_threshold targets = [read_roi(tgt, shape=self.shape) for tgt in self.targets] if self.stop_on_target: for target_mask in targets: mask = mask & ~target_mask seed_mask = read_roi(self.seed_roi, shape=self.shape) seeds = seeds_from_mask(seed_mask, self.seed_density, voxel_size) if ((self.interpolator == 'NearestNeighbor' and not self.probabilistic and not debug)): using_optimze = True peak_finder = NND_ClosestPeakSelector(model, data, mask, voxel_size) else: using_optimze = False interpolator_type = all_interpolators[self.interpolator] interpolator = interpolator_type(data, voxel_size, mask) peak_finder = ClosestPeakSelector(model, interpolator) # Set peak_finder parameters for start steps peak_finder.angle_limit = 90 model.peak_spacing = self.min_peak_spacing if self.seed_largest_peak: model.min_relative_peak = 1 else: model.min_relative_peak = self.min_relative_peak data_ornt = nib.io_orientation(self.affine) best_start = reorient_vectors(self.start_direction, 'ras', data_ornt) start_steps = closest_start(seeds, peak_finder, best_start) if self.probabilistic: interpolator = ResidualBootstrapWrapper(interpolator, model.B, min_signal=dmin) peak_finder = ClosestPeakSelector(model, interpolator) elif using_optimze and self.seed_largest_peak: peak_finder.reset_cache() # Reset peak_finder parameters for tracking peak_finder.angle_limit = self.max_turn_angle model.peak_spacing = self.min_peak_spacing model.min_relative_peak = self.min_relative_peak integrator = BoundryIntegrator(voxel_size, overstep=.1) streamlines = generate_streamlines(peak_finder, integrator, seeds, start_steps) if self.track_two_directions: start_steps = -start_steps streamlinesB = generate_streamlines(peak_finder, integrator, seeds, start_steps) streamlines = merge_streamlines(streamlines, streamlinesB) for target_mask in targets: streamlines = target(streamlines, target_mask, voxel_size) return streamlines
class TriXDomainFE(XDomainFE): name = 'TriXDomainFE' ''' Finite element discretization with dofs and mappings derived from the FE definition ''' mesh = tr.Instance(FETriangularMesh, ()) fets = tr.DelegatesTo('mesh') tree = ['mesh'] change = tr.Event(GEO=True) plot_backend = 'k3d' n_dofs = tr.Property def _get_n_dofs(self): return len(self.mesh.X_Id) * self.mesh.n_nodal_dofs eta_w = tr.Property r'''Weight factors for numerical integration. ''' def _get_eta_w(self): return self.fets.w_m Na_deta = tr.Property() r'''Derivatives of the shape functions in the integration points. ''' @tr.cached_property def _get_Na_deta(self): return np.einsum('imr->mri', self.fets.dN_imr) x_0 = tr.Property() r'''Derivatives of the shape functions in the integration points. ''' @tr.cached_property def _get_x_0(self): return self.mesh.X_Id x = tr.Property() r'''Derivatives of the shape functions in the integration points. ''' @tr.cached_property def _get_x(self): return self.x_0 F = tr.Property() r'''Derivatives of the shape functions in the integration points. ''' @tr.cached_property def _get_F(self): return self.mesh.I_Fi T_Fab = tr.Property(depends_on='+GEO') @tr.cached_property def _get_T_Fab(self): return self.F_L_bases[:, 0, :] I_Ei = tr.Property def _get_I_Ei(self): return self.F_N x_Eia = tr.Property(depends_on='+GEO') @tr.cached_property def _get_x_Eia(self): X_Eia = self.X_Id[self.I_Ei, :] X_E0a = X_Eia[:, 0, :] X_Eia -= X_E0a[:, np.newaxis, :] X_Eic = np.einsum('Eac,Eic->Eia', self.T_Fab, X_Eia) return X_Eic[...,:-1] # return X_Eia[..., :-1] def U2u(self, U_Eia): u0_Eia = U_Eia[...,:-1] return u0_Eia def xU2u(self, U_Eia): u1_Eia = np.einsum('Eab,Eib->Eia', self.T_Fab, U_Eia) u2_Eie = np.einsum('ea,Eia->Eie', DELTA23_ab, u1_Eia) # u2_Eie = np.einsum('ea,Eia->Eie', DELTA23_ab, U_Eia) return u2_Eie def f2F(self, f_Eid): F0_Eia = np.concatenate( [f_Eid, np.zeros_like(f_Eid[...,:1])], axis=-1) return F0_Eia def xf2F(self, f_Eid): F1_Eia = np.einsum('da,Eid->Eia', DELTA23_ab, f_Eid) F2_Eia = np.einsum('Eab,Eia->Eib', self.T_Fab, F1_Eia) return F2_Eia # return F1_Eia def k2K(self, K_Eiejf): K0_Eicjf = np.concatenate( [K_Eiejf, np.zeros_like(K_Eiejf[:,:,:1,:,:])], axis=2) K0_Eicjd = np.concatenate( [K0_Eicjf, np.zeros_like(K0_Eicjf[:,:,:,:,:1])], axis=4) return K0_Eicjd def xk2K(self, K_Eiejf): K1_Eiejf = np.einsum('ea,fb,Eiejf->Eiajb', DELTA23_ab, DELTA23_ab, K_Eiejf) # correct T_Eeafb = np.einsum('Eea,Efb->Eeafb', self.T_Fab, self.T_Fab) #K_Eab = np.einsum('Eeafb,ef->Eab', T_Eeafb, k_ef) K2_Eiajb = np.einsum('Eeafb,Eiejf->Eiajb', T_Eeafb, K1_Eiejf) #K2_Eicjd = np.einsum('Eca,Ebd,Eiajb->Eicjd', self.T_Fab, self.T_Fab, K1_Eicjd) #K2_Eicjd = np.einsum('Eac,Edb,Eiajb->Eicjd', self.T_Fab, self.T_Fab, K1_Eicjd) return K2_Eiajb # return K1_Eiejf I_CDij = tr.Property def _get_I_CDij(self): return self.mesh.I_CDij bc_J_F_xyz= tr.Property(depends_on='state_changed') @tr.cached_property def _get_bc_J_F_xyz(self): ix2 = int((self.mesh.n_phi_plus) / 2) F_I = self.I_CDij[ix2, :, 0, :].flatten() _, idx_remap = self.mesh.unique_node_map return idx_remap[F_I] bc_J_xyz = tr.Property(depends_on='state_changed') @tr.cached_property def _get_bc_J_xyz(self): I_M = self.I_CDij[(0, -1), :, (0, -1), :].flatten() _, idx_remap = self.mesh.unique_node_map J_M = idx_remap[I_M] return J_M bc_J_x = tr.Property(depends_on='state_changed') @tr.cached_property def _get_bc_J_x(self): I_M = self.I_CDij[:, (0, -1), :, (0, -1)].flatten() _, idx_remap = self.mesh.unique_node_map J_M = idx_remap[I_M] return J_M def setup_plot(self, pb): X_Ia = self.mesh.X_Ia.astype(np.float32) I_Fi = self.mesh.I_Fi.astype(np.uint32) X_Ma = X_Ia[self.bc_J_xyz] self.k3d_fixed_xyz = k3d.points(X_Ma) pb.plot_fig += self.k3d_fixed_xyz X_Ma = X_Ia[self.bc_J_x] self.k3d_fixed_x = k3d.points(X_Ma, color=0x22ffff) pb.plot_fig += self.k3d_fixed_x X_Ma = X_Ia[self.bc_J_F_xyz] self.k3d_load_z = k3d.points(X_Ma, color=0xff22ff) pb.plot_fig += self.k3d_load_z self.k3d_mesh = k3d.mesh(X_Ia, I_Fi, color=0x999999, side='double') pb.plot_fig += self.k3d_mesh def update_plot(self, pb): X_Ia = self.mesh.X_Ia.astype(np.float32) I_Fi = self.mesh.I_Fi.astype(np.uint32) self.k3d_fixed_xyz.positions = X_Ia[self.bc_J_xyz] self.k3d_fixed_x.positions = X_Ia[self.bc_J_x] self.k3d_load_z.positions = X_Ia[self.bc_J_F_xyz] mesh = self.k3d_mesh mesh.vertices = X_Ia mesh.indices = I_Fi B_Eso = tr.Property(depends_on='+GEO') @tr.cached_property def _get_B_Eso(self): xx_Ei, yy_Ei = np.einsum('...a->a...', self.x_Eia) # xx_Ei, yy_Ei = np.einsum('...a->a...', self.X_Id[self.F_N, :-1]) # xx_Ei, yy_Ei = np.einsum('...a->a...', self.mesh.X_Id[self.mesh.I_Fi, :-1]) y23 = yy_Ei[:, 1] - yy_Ei[:, 2] y31 = yy_Ei[:, 2] - yy_Ei[:, 0] y12 = yy_Ei[:, 0] - yy_Ei[:, 1] x32 = xx_Ei[:, 2] - xx_Ei[:, 1] x13 = xx_Ei[:, 0] - xx_Ei[:, 2] x21 = xx_Ei[:, 1] - xx_Ei[:, 0] x23 = -x32 y32 = -y23 y13 = -y31 J_Ear = np.array([[x13, y13], [x23, y23]]) J_Ear = np.einsum('ar...->...ar', J_Ear) det_J_E = np.linalg.det(J_Ear) O = np.zeros_like(y23) B_soE = np.array( [ [y23, O, y31, O, y12, O], [O, x32, O, x13, O, x21], [x32, y23, x13, y31, x21, y12] ] ) B_Eso = np.einsum('soE,E->Eso', B_soE, 1 / det_J_E) return B_Eso, det_J_E def map_U_to_field(self, U_o): print('') print('U_o,', U_o) U_Eia = U_o[self.o_Eia] # coordinate transform to local u_Eia = self.xU2u(U_Eia) u_Eo = u_Eia.reshape(-1, 6) B_Eso, _ = self.B_Eso eps_Eso = np.einsum( 'Eso,Eo->Es', B_Eso, u_Eo ) print('eps_Eso,', eps_Eso) return eps_Eso def map_field_to_F(self, sig_Es): # print('map_field_to_F:') # print('sig_Es:', sig_Es) B_Eso, det_J_E = self.B_Eso f_Eo = self.integ_factor * np.einsum( 'Eso,Es,E->Eo', B_Eso, sig_Es, det_J_E / 2 ) f_Eic = f_Eo.reshape(-1,3,2) # coordinate transform to global f_Eic = self.xf2F(f_Eic) _, n_i, n_c = f_Eic.shape f_Ei = f_Eic.reshape(-1, n_i * n_c) o_E = self.o_Eia.reshape(-1, n_i * n_c) return o_E.flatten(), f_Ei.flatten() def map_field_to_K(self, D_Est): # print('map_field_to_K:') #========================================================================== B_Eso, det_J_E = self.B_Eso k2_ij = self.integ_factor * np.einsum('Eso,Est,Etp,E->Eop', B_Eso, D_Est, B_Eso, det_J_E / 2) K_Eiejf = k2_ij.reshape(-1, 3, 2, 3, 2) K_Eicjd = self.xk2K(K_Eiejf) _, n_i, n_c, n_j, n_d = K_Eicjd.shape K_Eij = K_Eicjd.reshape(-1, n_i * n_c, n_j * n_d) o_Ei = self.o_Eia.reshape(-1, n_i * n_c) # print('K_Eij:', K_Eij) print('o_Ei:', o_Ei) return SysMtxArray(mtx_arr=K_Eij, dof_map_arr=o_Ei) # ========================================================================= # Property operators for initial configuration # ========================================================================= F0_normals = tr.Property(tr.Array, depends_on='X, L, F') r'''Normal facet vectors. ''' @tr.cached_property def _get_F0_normals(self): x_F = self.x_0[self.F] N_deta_ip = self.Na_deta r_deta = np.einsum('ajK,IKi->Iaij', N_deta_ip, x_F) Fa_normals = np.einsum('Iai,Iaj,ijk->Iak', r_deta[..., 0], r_deta[..., 1], EPS) return np.sum(Fa_normals, axis=1) sign_normals = tr.Property(tr.Array, depends_on='X,L,F') r'''Orientation of the normal in the initial state. This array is used to switch the normal vectors of the faces to be oriented in the positive sense of the z-axis. ''' @tr.cached_property def _get_sign_normals(self): return np.sign(self.F0_normals[:, 2]) F_N = tr.Property(tr.Array, depends_on='X,L,F') r'''Counter-clockwise enumeration. ''' @tr.cached_property def _get_F_N(self): turn_facets = np.where(self.sign_normals < 0) F_N = np.copy(self.F) F_N[turn_facets, :] = self.F[turn_facets, ::-1] return F_N # return self.mesh.I_Fi F_normals = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normals of the facets. ''' @tr.cached_property def _get_F_normals(self): n = self.Fa_normals return np.sum(n, axis=1) F_normals_0 = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normals of the facets. ''' @tr.cached_property def _get_F_normals_0(self): n = self.Fa_normals_0 return np.sum(n, axis=1) norm_F_normals = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normed normals of the facets. ''' @tr.cached_property def _get_norm_F_normals(self): n = self.F_normals mag_n = np.sqrt(np.einsum('...i,...i', n, n)) return n / mag_n[:, np.newaxis] norm_F_normals_0 = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normed normals of the facets. ''' @tr.cached_property def _get_norm_F_normals_0(self): n = self.F_normals_0 mag_n = np.sqrt(np.einsum('...i,...i', n, n)) return n / mag_n[:, np.newaxis] F_normals_du = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normals of the facets. ''' @tr.cached_property def _get_F_normals_du(self): n_du = self.Fa_normals_du return np.sum(n_du, axis=1) F_area = tr.Property(tr.Array, depends_on=INPUT) r'''Get the surface area of the facets. ''' @tr.cached_property def _get_F_area(self): a = self.Fa_area A = np.einsum('a,Ia->I', self.eta_w, a) return A # ========================================================================= # Potential energy # ========================================================================= F_V = tr.Property(tr.Array, depends_on=INPUT) r'''Get the total potential energy of gravity for each facet ''' @tr.cached_property def _get_F_V(self): eta_w = self.eta_w a = self.Fa_area ra = self.Fa_r F_V = np.einsum('a,Ia,Ia->I', eta_w, ra[..., 2], a) return F_V F_V_du = tr.Property(tr.Array, depends_on=INPUT) r'''Get the derivative of total potential energy of gravity for each facet with respect to each node and displacement component [FIi] ''' @tr.cached_property def _get_F_V_du(self): r = self.Fa_r a = self.Fa_area a_dx = self.Fa_area_du r3_a_dx = np.einsum('Ia,IaJj->IaJj', r[..., 2], a_dx) N_eta_ip = self.Na r3_dx = np.einsum('aK,KJ,j->aJj', N_eta_ip, DELTA, DELTA[2, :]) a_r3_dx = np.einsum('Ia,aJj->IaJj', a, r3_dx) F_V_du = np.einsum('a,IaJj->IJj', self.eta_w, (a_r3_dx + r3_a_dx)) return F_V_du # ========================================================================= # Line vectors # ========================================================================= F_L_vectors_0 = tr.Property(tr.Array, depends_on=INPUT) r'''Get the cycled line vectors around the facet The cycle is closed - the first and last vector are identical. .. math:: v_{pld} \;\mathrm{where} \; p\in\mathcal{F}, l\in (0,1,2), d\in (0,1,2) with the indices :math:`p,l,d` representing the facet, line vector around the facet and and vector component, respectively. ''' @tr.cached_property def _get_F_L_vectors_0(self): F_N = self.F_N # F_N is cycled counter clockwise return self.x_0[F_N[:, (1, 2, 0)]] - self.x_0[F_N[:, (0, 1, 2)]] F_L_vectors = tr.Property(tr.Array, depends_on=INPUT) r'''Get the cycled line vectors around the facet The cycle is closed - the first and last vector are identical. .. math:: v_{pld} \;\mathrm{where} \; p\in\mathcal{F}, l\in (0,1,2), d\in (0,1,2) with the indices :math:`p,l,d` representing the facet, line vector around the facet and and vector component, respectively. ''' @tr.cached_property def _get_F_L_vectors(self): F_N = self.F_N # F_N is cycled counter clockwise return self.x[F_N[:, (1, 2, 0)]] - self.x[F_N[:, (0, 1, 2)]] F_L_vectors_du = tr.Property(tr.Array, depends_on=INPUT) r'''Get the derivatives of the line vectors around the facets. .. math:: \pard{v_{pld}}{x_{Ie}} \; \mathrm{where} \; p \in \mathcal{F}, \in (0,1,2), d\in (0,1,2), I\in \mathcal{N}, e \in (0,1,3) with the indices :math:`p,l,d,I,e` representing the facet, line vector around the facet and and vector component, node vector and and its component index, respectively. This array works essentially as an index function delivering -1 for the components of the first node in each dimension and +1 for the components of the second node in each dimension. For a facet :math:`p` with lines :math:`l` and component :math:`d` return the derivatives with respect to the displacement of the node :math:`I` in the direction :math:`e`. .. math:: \bm{a}_1 = \bm{x}_2 - \bm{x}_1 \\ \bm{a}_2 = \bm{x}_3 - \bm{x}_2 \\ \bm{a}_3 = \bm{x}_1 - \bm{x}_3 The corresponding derivatives are then .. math:: \pard{\bm{a}_1}{\bm{u}_1} = -1, \;\;\; \pard{\bm{a}_1}{\bm{u}_2} = 1 \\ \pard{\bm{a}_2}{\bm{u}_2} = -1, \;\;\; \pard{\bm{a}_2}{\bm{u}_3} = 1 \\ \pard{\bm{a}_3}{\bm{u}_3} = -1, \;\;\; \pard{\bm{a}_3}{\bm{u}_1} = 1 \\ ''' def _get_F_L_vectors_du(self): return self.L_vectors_du[self.F_L] F_L_vectors_dul = tr.Property(tr.Array, depends_on=INPUT) def _get_F_L_vectors_dul(self): return self.L_vectors_dul[self.F_L] norm_F_L_vectors = tr.Property(tr.Array, depends_on=INPUT) r'''Get the cycled line vectors around the facet The cycle is closed - the first and last vector are identical. ''' @tr.cached_property def _get_norm_F_L_vectors(self): v = self.F_L_vectors mag_v = np.sqrt(np.einsum('...i,...i', v, v)) return v / mag_v[..., np.newaxis] norm_F_L_vectors_du = tr.Property(tr.Array, depends_on=INPUT) '''Get the derivatives of cycled line vectors around the facet ''' @tr.cached_property def _get_norm_F_L_vectors_du(self): v = self.F_L_vectors v_du = self.F_L_vectors_du # @UnusedVariable mag_v = np.einsum('...i,...i', v, v) # @UnusedVariable # @todo: finish the chain rule raise NotImplemented # ========================================================================= # Orthonormal basis of each facet. # ========================================================================= F_L_bases = tr.Property(tr.Array, depends_on=INPUT) r'''Line bases around a facet. ''' @tr.cached_property def _get_F_L_bases(self): l = self.norm_F_L_vectors n = self.norm_F_normals lxn = np.einsum('...li,...j,...kij->...lk', l, n, EPS) n_ = n[:, np.newaxis, :] * np.ones((1, 3, 1), dtype='float_') T = np.concatenate([l[:, :, np.newaxis, :], -lxn[:, :, np.newaxis, :], n_[:, :, np.newaxis, :]], axis=2) return T F_L_bases_du = tr.Property(tr.Array, depends_on=INPUT) r'''Derivatives of the line bases around a facet. ''' @tr.cached_property def _get_F_L_bases_du(self): '''Derivatives of line bases''' raise NotImplemented # ========================================================================= # Sector angles # ========================================================================= F_theta = tr.Property(tr.Array, depends_on=INPUT) '''Get the sector angles :math:`\theta` within a facet. ''' @tr.cached_property def _get_F_theta(self): v = self.F_L_vectors a = -v[:, (2, 0, 1), :] b = v[:, (0, 1, 2), :] return get_theta(a, b) F_theta_du = tr.Property(tr.Array, depends_on=INPUT) r'''Get the derivatives of sector angles :math:`\theta` within a facet. ''' @tr.cached_property def _get_F_theta_du(self): v = self.F_L_vectors v_du = self.F_L_vectors_du a = -v[:, (2, 0, 1), :] b = v[:, (0, 1, 2), :] a_du = -v_du[:, (2, 0, 1), ...] b_du = v_du[:, (0, 1, 2), ...] return get_theta_du(a, a_du, b, b_du) Fa_normals_du = tr.Property '''Get the derivatives of the normals with respect to the node displacements. ''' def _get_Fa_normals_du(self): x_F = self.x[self.F_N] N_deta_ip = self.Na_deta NN_delta_eps_x1 = np.einsum('aK,aL,KJ,dli,ILl->IaiJd', N_deta_ip[:, 0, :], N_deta_ip[:, 1, :], DELTA, EPS, x_F) NN_delta_eps_x2 = np.einsum('aK,aL,LJ,kdi,IKk->IaiJd', N_deta_ip[:, 0, :], N_deta_ip[:, 1, :], DELTA, EPS, x_F) n_du = NN_delta_eps_x1 + NN_delta_eps_x2 return n_du Fa_area_du = tr.Property '''Get the derivatives of the facet area with respect to node displacements. ''' def _get_Fa_area_du(self): a = self.Fa_area n = self.Fa_normals n_du = self.Fa_normals_du a_du = np.einsum('Ia,Iak,IakJd->IaJd', 1 / a, n, n_du) return a_du Fa_normals = tr.Property '''Get normals of the facets. ''' def _get_Fa_normals(self): x_F = self.x[self.F_N] N_deta_ip = self.Na_deta r_deta = np.einsum('ajK,IKi->Iaij', N_deta_ip, x_F) return np.einsum('Iai,Iaj,ijk->Iak', r_deta[..., 0], r_deta[..., 1], EPS) Fa_normals_0 = tr.Property '''Get normals of the facets. ''' def _get_Fa_normals_0(self): x_F = self.x_0[self.F_N] N_deta_ip = self.Na_deta r_deta = np.einsum('ajK,IKi->Iaij', N_deta_ip, x_F) return np.einsum('Iai,Iaj,ijk->Iak', r_deta[..., 0], r_deta[..., 1], EPS) Fa_area = tr.Property '''Get the surface area of the facets. ''' def _get_Fa_area(self): n = self.Fa_normals a = np.sqrt(np.einsum('Iai,Iai->Ia', n, n)) return a Fa_r = tr.Property '''Get the reference vector to integrations point in each facet. ''' def _get_Fa_r(self): x_F = self.x[self.F_N] N_eta_ip = self.Na r = np.einsum('aK,IKi->Iai', N_eta_ip, x_F) return r
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 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]
class TriXDomainMITC(XDomainFE): name = 'TriXDomainFE' ''' Finite element discretization with dofs and mappings derived from the FE definition ''' mesh = tr.Instance(FETriangularMesh) def _mesh_default(self): return FETriangularMesh(fets=FETS2DMITC()) fets = tr.DelegatesTo('mesh') tree = ['mesh'] change = tr.Event(GEO=True) plot_backend = 'k3d' n_dofs = tr.Property def _get_n_dofs(self): return len(self.mesh.X_Id) * self.mesh.n_nodal_dofs eta_w = tr.Property r'''Weight factors for numerical integration. ''' def _get_eta_w(self): return self.fets.w_m # T_Fab = tr.Property(depends_on='+GEO') # @tr.cached_property # def _get_T_Fab(self): # return self.F_L_bases[:, 0, :] I_Ei = tr.Property def _get_I_Ei(self): return self.F_N # x_Eia = tr.Property(depends_on='+GEO') # @tr.cached_property # def _get_x_Eia(self): # X_Eia = self.X_Id[self.I_Ei, :] # X_E0a = X_Eia[:, 0, :] # X_Eia -= X_E0a[:, np.newaxis, :] # X_Eic = np.einsum('Eac,Eic->Eia', self.T_Fab, X_Eia) # return X_Eic[...,:-1] # def U2u(self, U_Eia): # u0_Eia = U_Eia[...,:-1] # return u0_Eia # # def xU2u(self, U_Eia): # u1_Eia = np.einsum('Eab,Eib->Eia', self.T_Fab, U_Eia) # # u2_Eie = np.einsum('ea,Eia->Eie', DELTA23_ab, u1_Eia) # # return u2_Eie # return u1_Eia # # def f2F(self, f_Eid): # F0_Eia = np.concatenate( [f_Eid, np.zeros_like(f_Eid[...,:1])], axis=-1) # return F0_Eia # # def xf2F(self, f_Eid): # # F1_Eia = np.einsum('da,Eid->Eia', DELTA23_ab, f_Eid) # F2_Eia = np.einsum('Eab,Eia->Eib', self.T_Fab, f_Eid) # return F2_Eia # # def k2K(self, K_Eiejf): # K0_Eicjf = np.concatenate([K_Eiejf, np.zeros_like(K_Eiejf[:,:,:1,:,:])], axis=2) # K0_Eicjd = np.concatenate([K0_Eicjf, np.zeros_like(K0_Eicjf[:,:,:,:,:1])], axis=4) # return K0_Eicjd # # def xk2K(self, K_Eiejf): # # K1_Eiejf = np.einsum('ea,fb,Eiejf->Eiajb', DELTA23_ab, DELTA23_ab, K_Eiejf) # correct # T_Eeafb = np.einsum('Eea,Efb->Eeafb', self.T_Fab, self.T_Fab) # #K_Eab = np.einsum('Eeafb,ef->Eab', T_Eeafb, k_ef) # K2_Eiajb = np.einsum('Eeafb,Eiejf->Eiajb', T_Eeafb, K_Eiejf) # #K2_Eicjd = np.einsum('Eca,Ebd,Eiajb->Eicjd', self.T_Fab, self.T_Fab, K1_Eicjd) # #K2_Eicjd = np.einsum('Eac,Edb,Eiajb->Eicjd', self.T_Fab, self.T_Fab, K1_Eicjd) # return K2_Eiajb def setup_plot(self, pb): X_Ia = self.mesh.X_Ia.astype(np.float32) I_Fi = self.mesh.I_Fi.astype(np.uint32) X_Ma = X_Ia[self.bc_J_xyz] self.k3d_fixed_xyz = k3d.points(X_Ma) pb.plot_fig += self.k3d_fixed_xyz X_Ma = X_Ia[self.bc_J_x] self.k3d_fixed_x = k3d.points(X_Ma, color=0x22ffff) pb.plot_fig += self.k3d_fixed_x X_Ma = X_Ia[self.bc_J_F_xyz] self.k3d_load_z = k3d.points(X_Ma, color=0xff22ff) pb.plot_fig += self.k3d_load_z self.k3d_mesh = k3d.mesh(X_Ia, I_Fi, color=0x999999, side='double') pb.plot_fig += self.k3d_mesh def update_plot(self, pb): X_Ia = self.mesh.X_Ia.astype(np.float32) I_Fi = self.mesh.I_Fi.astype(np.uint32) self.k3d_fixed_xyz.positions = X_Ia[self.bc_J_xyz] self.k3d_fixed_x.positions = X_Ia[self.bc_J_x] self.k3d_load_z.positions = X_Ia[self.bc_J_F_xyz] mesh = self.k3d_mesh mesh.vertices = X_Ia mesh.indices = I_Fi # def _get_du_dr_Fmra(self, U_o): # dh_imr = self.fets.dh_imr # dht_imr = self.fets.dht_imr # _, v1_Fid, v2_Fid = self.v_vectors # a = self.fets.a # # # Calculating du_dr # U_o = np.arange(3 * 5) # TODO U_o comes from function arguments, this is just for testing # nodes_num = self.mesh.X_Id.shape[0] # U_Ie = np.reshape(U_o, (nodes_num, self.fets.n_nodal_dofs)) # U_Fie = U_Ie[self.mesh.I_Fi] # disp_U_Fia = U_Fie[..., :3] # rot_U_Fib = U_Fie[..., 3:] # du_dr1_Fmria = np.einsum('Fia, imr ->Fmria', disp_U_Fia, dh_imr) # du_dr1_Fmra = np.sum(du_dr1_Fmria, axis=3) # # alpha_idx = 0 # beta_idx = 1 # alpha_Fi1 = rot_U_Fib[..., alpha_idx, np.newaxis] # beta_Fi1 = rot_U_Fib[..., beta_idx, np.newaxis] # v2_alpha_Fid = v2_Fid * alpha_Fi1 # v1_beta_Fid = v1_Fid * beta_Fi1 # v1_v2_dif_Fid = v1_beta_Fid - v2_alpha_Fid # du_dr2_Fmria = np.einsum('Fia, imr ->Fmria', 0.5 * a * v1_v2_dif_Fid, dht_imr) # du_dr2_Fmra = np.sum(du_dr2_Fmria, axis=3) # du_dr_Fmra = du_dr1_Fmra + du_dr2_Fmra # return du_dr_Fmra v_vectors = tr.Property(depends_on='+GEO') @tr.cached_property def _get_v_vectors(self): # See P. 472 FEM by Zienkiewicz (ISBN: 1856176339) # Calculating v_n (director vector) n_Fd = self.norm_F_normals el_nodes_num = 3 Vn_Fid = np.tile(n_Fd, (1, 1, el_nodes_num)).reshape(n_Fd.shape[0], n_Fd.shape[1], el_nodes_num) # Calculating v1 and v2 (vectors perpendicular to director vector) # Finding V1 by getting the minimum component of V3 vector according to Zienkiewicz min_Fi1 = np.abs(Vn_Fid).argmin(axis=2) min_Fi1 = min_Fi1[..., np.newaxis] tmp_Fid = np.zeros_like(Vn_Fid, dtype=np.int_) tmp_Fid[..., :] = np.arange(3) min_mask_Fid = tmp_Fid == min_Fi1 e_x_min_Fid = min_mask_Fid * 1 V1_Fid = np.cross(e_x_min_Fid, Vn_Fid) # Or take simply e_2 to calculate V1_Fid as in Bathe lecture # e_2_Fid = np.zeros_like(Vn_Fid) # e_2_Fid[..., 1] = 1 # V1_Fid = np.cross(e_2_Fid, Vn_Fid) V2_Fid = np.cross(Vn_Fid, V1_Fid) v1_Fid = self._normalize(V1_Fid) v2_Fid = self._normalize(V2_Fid) return Vn_Fid, v1_Fid, v2_Fid def _normalize(self, V_Fid): mag_n = np.sqrt(np.einsum('...i,...i', V_Fid, V_Fid)) v_Fid = V_Fid / mag_n[:, np.newaxis] return v_Fid dx_dr_Fmrd = tr.Property(depends_on='+GEO') @tr.cached_property def _get_dx_dr_Fmrd(self): Vn_Fid, _, _ = self.v_vectors dh_imr = self.fets.dh_imr dht_imr = self.fets.dht_imr X_Fid = self.X_Id[self.F_N] a = self.fets.a # thickness (TODO, make it available as input) dx_dr1_Fmrid = np.einsum('Fid, imr -> Fmrid', X_Fid, dh_imr) dx_dr2_Fmrid = np.einsum('Fid, imr -> Fmrid', 0.5 * a * Vn_Fid, dht_imr) dx_dr1_Fmrd = np.sum(dx_dr1_Fmrid, axis=3) dx_dr2_Fmrd = np.sum(dx_dr2_Fmrid, axis=3) dx_dr_Fmrd = dx_dr1_Fmrd + dx_dr2_Fmrd return dx_dr_Fmrd # dN_dr_Emrco = tr.Property(depends_on='+GEO') # @tr.cached_property # def _get_dN_dr_Emrco(self): # # thickness # a = self.fets.a # _, v1_Fid, v2_Fid = self.v_vectors # dh_imr = self.fets.dh_imr # dht_imr = self.fets.dht_imr # # element_dofs = 3 * self.fets.n_nodal_dofs # dN_dr_Emrco = np.zeros((*self.dx_dr_Fmrd.shape, element_dofs)) # # Filling first 3x3 elements from N matrices # dofs = 5 # # Looping r, s, t # for i in range(3): # # Looping h1, h2, h3 # for j in range(3): # dN_dr_Emrco[..., i, :, 0 + j * dofs:3 + j * dofs] = np.identity(3) * dh_imr[ # j, 0, i] # dh1/dr (which is the same for all gaus points) # # Example: dh_imr[2, 1, 0] is dh3/dr for the second Gauss point # N4_Fmrai = np.einsum('Fia, imr ->Fmrai', -0.5 * a * v2_Fid, dht_imr) # N5_Fmrai = np.einsum('Fia, imr ->Fmrai', +0.5 * a * v1_Fid, dht_imr) # dN_dr_Emrco[..., :, [3, 8, 13]] = N4_Fmrai # dN_dr_Emrco[..., :, [4, 9, 14]] = N5_Fmrai # return dN_dr_Emrco B_Emiabo = tr.Property(depends_on='+GEO') # @tr.cached_property def _get_B_Emiabo(self): delta35_co = np.zeros((3, 5), dtype='f') delta35_co[(0, 1, 2), (0, 1, 2)] = 1 delta25_vo = np.zeros((2, 5), dtype='f') delta25_vo[(0, 1), (3, 4)] = 1 _, v1_Fid, v2_Fid = self.v_vectors V_Ficv = np.zeros((*v2_Fid.shape, 2), dtype='f') # TODO, one of these maybe should be multiplied with -1 and they might need to be flipped, see x formula V_Ficv[..., 0] = v1_Fid V_Ficv[..., 1] = -v2_Fid # Thickness a a = self.fets.a dN_imr = self.fets.dh_imr dNt_imr = self.fets.dht_imr delta = np.identity(3) Diff1_abcd = 0.5 * (np.einsum('ac,bd->abcd', delta, delta) + np.einsum('ad,bc->abcd', delta, delta)) J_Fmrd = self.dx_dr_Fmrd inv_J_Fmrd = np.linalg.inv(J_Fmrd) det_J_Fm = np.linalg.det(J_Fmrd) B1_Emiabo = np.einsum('abcd, imr, co, Emrd -> Emiabo', Diff1_abcd, dN_imr, delta35_co, inv_J_Fmrd) B2_Emiabo = np.einsum('abcd, imr, Eicv, vo, Emrd -> Emiabo', Diff1_abcd, dNt_imr, 0.5 * a * V_Ficv, delta25_vo, inv_J_Fmrd) B_Emiabo = B1_Emiabo + B2_Emiabo # B_Emiabo = np.flip(B_Emiabo, 2) return B_Emiabo, det_J_Fm B_Empf = tr.Property(depends_on='+GEO') # @tr.cached_property def _get_B_Empf(self): # TODO: here we're eliminating eps_33 because this is the assumption for shell (for eps IN ELEMENT LEVEL) # therefore, B_Empf must be already converted to element coords system and not using the global one B_Emiabo, _ = self.B_Emiabo # Mapping ab to p (3x3 -> 5) B_Emipo = B_Emiabo[:, :, :, (0, 1, 0, 1, 2), (0, 1, 1, 2, 0), :] # What follows is to adjust shear strains: [e_xx, e_yy, e_xy + e_yx, e_yz + e_zy, e_zx + e_xz] B_Emipo[:, :, :, 2:, :] = B_Emipo[:, :, :, 2:, :] + B_Emiabo[:, :, :, (1, 2, 0), (0, 1, 2), :] # B_Emipo[:, :, :, 2:, :] = 2 * B_Emipo[:, :, :, 2:, :] E, m, i, p, o = B_Emipo.shape B_Empio = np.einsum('Emipo->Empio', B_Emipo) B_Empf = B_Empio.reshape((E, m, p, i * o)) # p: index with max value 5 # f: index with max value 15 return B_Empf # Transformation matrix, see P. 475 (Zienkiewicz FEM book) def get_theta(self): J_Fmrd = self.dx_dr_Fmrd dx_dr_Fmd = J_Fmrd[..., 0, :] dx_ds_Fmd = J_Fmrd[..., 1, :] V3_Fmd = np.cross(dx_dr_Fmd, dx_ds_Fmd) # Finding V1 by simply selecting e1 e_2_Fmd = np.zeros_like(V3_Fmd) e_2_Fmd[..., 0] = 1 V1_Fmd = np.cross(e_2_Fmd, V3_Fmd) # Finding V1 by getting the minimum component of V3 vector according to Zienkiewicz # min_Fm1 = np.abs(V3_Fmd).argmin(axis=2) # min_Fm1 = min_Fm1[..., np.newaxis] # tmp_Fmd = np.zeros_like(V3_Fmd, dtype=np.int_) # tmp_Fmd[..., :] = np.arange(3) # min_mask_Fmd = tmp_Fmd == min_Fm1 # e_x_min_Fmd = min_mask_Fmd * 1 # V1_Fmd = np.cross(e_x_min_Fmd, V3_Fmd) V2_Fmd = np.cross(V3_Fmd, V1_Fmd) v1_Fmd = self._normalize(V1_Fmd) v2_Fmd = self._normalize(V2_Fmd) v3_Fmd = self._normalize(V3_Fmd) theta_Fmdj = np.zeros((*v1_Fmd.shape, 3), dtype='f') theta_Fmdj = np.einsum('Fmdj->Fmjd', theta_Fmdj) theta_Fmdj[..., 0, :] = v1_Fmd theta_Fmdj[..., 1, :] = v2_Fmd theta_Fmdj[..., 2, :] = v3_Fmd return theta_Fmdj def map_U_to_field(self, U_o): # print('map_U_to_field') # # For testing with one element: # U_io = np.array([[1, 0, 0, 0, 0], # [0, 0, 0.5, 0, 0], # [0, 1, 0, 0, 0]], dtype=np.float_) print('U_o:', U_o) # TODO: check if the transformation caused by following line is needed # U_Eio = U_o[self.o_Eia] U_Eio = U_o.reshape((-1, self.fets.n_nodal_dofs))[self.F_N] B_Emiabo, _ = self.B_Emiabo eps_Emab = np.einsum('Emiabo, Eio -> Emab', B_Emiabo, U_Eio) eps_Emp = eps_Emab[:, :, (0, 1, 0, 1, 2), (0, 1, 1, 2, 0)] # What follows is to adjust shear strains: [e_xx, e_yy, e_xy + e_yx, e_yz + e_zy, e_zx + e_xz] eps_Emp[:, :, 2:] = eps_Emp[:, :, 2:] + eps_Emab[:, :, (1, 2, 0), (0, 1, 2)] # eps_Emp[:, :, 2:] = 2 * eps_Emp[:, :, 2:] return eps_Emp def map_field_to_F(self, sig_Ems): # print('map_field_to_F') print('sig_Es', sig_Ems) _, det_J_Fm = self.B_Emiabo f_Emf = self.integ_factor * np.einsum( 'm, Emsf, Ems, Em -> Emf', self.fets.w_m, self.B_Empf, sig_Ems, det_J_Fm) f_Ef = np.sum(f_Emf, axis=1) # o_Ei = self.o_Eia.reshape(-1, 3 * 5) # print('o_Ei:', o_Ei) o_Ei = self.o_Ia[self.F_N].reshape(-1, 3 * self.fets.n_nodal_dofs) return o_Ei.flatten(), f_Ef.flatten() def map_field_to_K(self, D_Est): # print('map_field_to_K') w_m = self.fets.w_m # Gauss points weights _, det_J_Fm = self.B_Emiabo B_Empf = self.B_Empf k2_Emop = self.integ_factor * np.einsum( 'm, Empf, Ept, Emtq, Em -> Emfq', w_m, B_Empf, D_Est, B_Empf, det_J_Fm) k2_Eop = np.sum(k2_Emop, axis=1) # o_Ei = self.o_Eia.reshape(-1, 3 * 5) # print('o_Ei:', o_Ei) o_Ei = self.o_Ia[self.F_N].reshape(-1, 3 * self.fets.n_nodal_dofs) return SysMtxArray(mtx_arr=k2_Eop, dof_map_arr=o_Ei) # O_Eo = tr.Property(tr.Array, depends_on='X, L, F') # @tr.cached_property # def _get_O_Eo(self): # return self.o_Ia[self.F_N].reshape(-1, 3 * self.fets.n_nodal_dofs) # ========================================================================= # Property operators for initial configuration # ========================================================================= F0_normals_Fk = tr.Property(tr.Array, depends_on='X, L, F') r'''Normals of the facets in the initial state (before reordering indices).''' @tr.cached_property def _get_F0_normals_Fk(self): n0_Fmk = self._get_normals_Fmk(X_Fid=self.X_Id[self.mesh.I_Fi]) return np.sum(n0_Fmk, axis=1) Fa_normals_Fmk = tr.Property '''Normals of the facets after reordering indices.''' def _get_Fa_normals_Fmk(self): n_Fmk = self._get_normals_Fmk(X_Fid=self.X_Id[self.F_N]) return n_Fmk def _get_normals_Fmk(self, X_Fid): dh_mri = np.einsum('imr->mri', self.fets.dh_imr) # dh_Fimr = self.dh_Fimr ?? r_deta_Fmdr = np.einsum('mri, Fid->Fmdr', dh_mri, X_Fid) return np.einsum('Fmi,Fmj,ijk->Fmk', r_deta_Fmdr[..., 0], r_deta_Fmdr[..., 1], EPS) sign_normals_F = tr.Property(tr.Array, depends_on='X,L,F') r'''Orientation of the normal in the initial state. This array is used to switch the normal vectors of the faces to be oriented in the positive sense of the z-axis. ''' @tr.cached_property def _get_sign_normals_F(self): # TODO: this takes the sign of the z component of the normal, in 3d this could be not sufficient return np.sign(self.F0_normals_Fk[:, 2]) norm_F_normals = tr.Property(tr.Array, depends_on=INPUT) r'''Get the normed normals of the facets after reordering F indices.''' @tr.cached_property def _get_norm_F_normals(self): Fa_normals_Fmk = self.Fa_normals_Fmk n_Fk = np.sum(Fa_normals_Fmk, axis=1) mag_n = np.sqrt(np.einsum('...i,...i', n_Fk, n_Fk)) return n_Fk / mag_n[:, np.newaxis] F_N = tr.Property(tr.Array, depends_on='X,L,F') r'''Counter-clockwise enumeration.''' @tr.cached_property def _get_F_N(self): # TODO: following test may be not valid in 3D shells case! other condition is to be taken turn_facets_F = np.where(self.sign_normals_F < 0) F_Fi = np.copy(self.mesh.I_Fi) F_Fi[turn_facets_F, :] = self.mesh.I_Fi[turn_facets_F, ::-1] # return self.mesh.I_Fi # for testing.. return F_Fi # TODO: when you get the first benchmark with one element working you may need to enforce these instead of dh_imr.. dh_Fimr = tr.Property(tr.Array, depends_on=INPUT) # @tr.cached_property def _get_dh_Fimr(self): dh_imr = self.fets.dh_imr facets_num = self.F_N.shape[0] dh_Fimr = np.copy(np.broadcast_to(dh_imr, (facets_num, *dh_imr.shape))) turn_facets = np.where(self.sign_normals_F < 0) dh_Fimr[turn_facets, ...] = dh_Fimr[turn_facets, ::-1, ...] return dh_Fimr dht_Fimr = tr.Property(tr.Array, depends_on=INPUT) # @tr.cached_property def _get_dht_Fimr(self): dht_imr = self.fets.dht_imr facets_num = self.F_N.shape[0] dht_Fimr = np.copy( np.broadcast_to(dht_imr, (facets_num, *dht_imr.shape))) turn_facets = np.where(self.sign_normals_F < 0) dht_Fimr[turn_facets, ...] = dht_Fimr[turn_facets, ::-1, ...] return dht_Fimr
class InelStateEvolution(bu.InteractiveModel): name = 'State evolution' slider_exp = tr.WeakRef(bu.InteractiveModel) t_slider = bu.Float(0) t_max = bu.Float(1.001) t_arr = tr.DelegatesTo('slider_exp') Sig_arr = tr.DelegatesTo('slider_exp') Eps_arr = tr.DelegatesTo('slider_exp') s_x_t = tr.DelegatesTo('slider_exp') s_y_t = tr.DelegatesTo('slider_exp') w_t = tr.DelegatesTo('slider_exp') iter_t = tr.DelegatesTo('slider_exp') ipw_view = bu.View(bu.Item('t_max', latex=r't_{\max}', readonly=True), time_editor=bu.HistoryEditor(var='t_slider', low=0, max_var='t_max', n_steps=50)) def plot_omega_NT(self, ax, **kw): s_x_pi_, s_y_pi_, w_pi_, z_, alpha_x_, alpha_y_, omega_T_, omega_N_ = self.Eps_arr.T ax.plot(omega_N_, omega_T_, **kw) ax.set_xlabel(r'$\omega_\mathrm{N}$') ax.set_ylabel(r'$\omega_\mathrm{T}$') def plot_Sig_Eps(self, axes): ax1, ax11, ax2, ax22, ax3, ax33, ax4 = axes colors = ['blue', 'red', 'green', 'black', 'magenta'] t = self.t_arr s_x_pi_, s_y_pi_, w_pi_, z_, alpha_x_, alpha_y_, omega_T_, omega_N_ = self.Eps_arr.T tau_x_pi_, tau_y_pi_, sig_pi_, Z_, X_x_, X_y_, Y_T_, Y_N_ = self.Sig_arr.T n_step = len(s_x_pi_) idx = np.argmax(self.t_slider < self.t_arr) # slip path in 2d def get_cum_s(s_x, s_y): d_s_x, d_s_y = s_x[1:] - s_x[:-1], s_y[1:] - s_y[:-1] d_s = np.hstack([0, np.sqrt(d_s_x**2 + d_s_y**2)]) return cumtrapz(d_s, initial=0) s_t = get_cum_s(self.s_x_t, self.s_y_t) s_pi_t = get_cum_s(s_x_pi_, s_y_pi_) w_t = self.w_t w_pi_t = w_pi_ tau_pi = np.sqrt(tau_x_pi_**2 + tau_y_pi_**2) ax1.set_title('stress - displacement') ax1.plot(t, tau_pi, '--', color='darkgreen', label=r'$||\tau||$') ax1.fill_between(t, tau_pi, 0, color='limegreen', alpha=0.1) ax1.plot(t, sig_pi_, '--', color='olivedrab', label=r'$\sigma$') ax1.fill_between(t, sig_pi_, 0, color='olivedrab', alpha=0.1) ax1.set_ylabel(r'$|| \tau ||, \sigma$') ax1.set_xlabel('$t$') ax1.plot(t[idx], 0, marker='H', color='red') ax1.legend() ax11.plot(t, s_t, color='darkgreen', label=r'$||s||$') ax11.plot(t, s_pi_t, '--', color='orange', label=r'$||s^\pi||$') ax11.plot(t, w_t, color='olivedrab', label=r'$w$') ax11.plot(t, w_pi_t, '--', color='chocolate', label=r'$w^\pi$') ax11.set_ylabel(r'$|| s ||, w$') ax11.legend() mpl_align_yaxis(ax1, 0, ax11, 0) ax2.set_title('energy release rate - damage') ax2.plot(t, Y_N_, '--', color='darkgray', label=r'$Y_N$') ax2.fill_between(t, Y_N_, 0, color='darkgray', alpha=0.15) ax2.plot(t, Y_T_, '--', color='darkslategray', label=r'$Y_T$') ax2.fill_between(t, Y_T_, 0, color='darkslategray', alpha=0.05) ax2.set_xlabel('$t$') ax2.set_ylabel('$Y$') ax2.plot(t[idx], 0, marker='H', color='red') ax2.legend() ax22.plot(t, omega_N_, color='darkgray', label=r'$\omega_N$') ax22.plot(t, omega_T_, color='darkslategray', label=r'$\omega_T$') ax22.set_ylim(ymax=1) ax22.set_ylabel(r'$\omega$') ax22.legend() ax3.set_title('hardening force - displacement') alpha_t = np.sqrt(alpha_x_**2 + alpha_y_**2) X_t = np.sqrt(X_x_**2 + X_y_**2) ax3.plot(t, Z_, '--', color='darkcyan', label=r'$Z$') ax3.fill_between(t, Z_, 0, color='darkcyan', alpha=0.05) ax3.plot(t, X_t, '--', color='darkslateblue', label=r'$X$') ax3.fill_between(t, X_t, 0, color='darkslateblue', alpha=0.05) ax3.set_ylabel(r'$Z, X$') ax3.set_xlabel('$t$') ax3.plot(t[idx], 0, marker='H', color='red') ax3.legend() ax33.plot(t, z_, color='darkcyan', label=r'$z$') ax33.plot(t, alpha_t, color='darkslateblue', label=r'$\alpha$') ax33.set_ylabel(r'$z, \alpha$') ax33.legend(loc='lower left') slide_model = self.slider_exp.slide_model slide_model.plot_f_state(ax4, self.Eps_arr[idx, :], self.Sig_arr[idx, :]) @staticmethod def subplots(fig): ((ax1, ax2), (ax3, ax4)) = fig.subplots(2, 2) ax11 = ax1.twinx() ax22 = ax2.twinx() ax33 = ax3.twinx() return ax1, ax11, ax2, ax22, ax3, ax33, ax4 def update_plot(self, axes): self.plot_Sig_Eps(axes)
class EnergyDissipation(bu.InteractiveModel): name='Energy' colors = dict( # color associations stored_energy = 'darkgreen', # recoverable free_energy_kin = 'darkcyan', # freedom - sky free_energy_iso = 'darkslateblue', # freedom - sky plastic_diss_s = 'darkorange', # fire - heat plastic_diss_w = 'red', # fire - heat damage_diss_s = 'darkgray', # ruined damage_diss_w = 'black' # ruined ) slider_exp = tr.WeakRef(bu.InteractiveModel) t_arr = tr.DelegatesTo('slider_exp') Sig_arr = tr.DelegatesTo('slider_exp') Eps_arr = tr.DelegatesTo('slider_exp') s_x_t = tr.DelegatesTo('slider_exp') s_y_t = tr.DelegatesTo('slider_exp') w_t = tr.DelegatesTo('slider_exp') iter_t = tr.DelegatesTo('slider_exp') show_iter = bu.Bool(False) E_plastic_work = bu.Bool(False) E_iso_free_energy = bu.Bool(True) E_kin_free_energy = bu.Bool(True) E_plastic_diss = bu.Bool(True) E_damage_diss = bu.Bool(True) ipw_view = bu.View( bu.Item('show_iter'), bu.Item('E_damage_diss'), bu.Item('E_plastic_work'), bu.Item('E_iso_free_energy'), bu.Item('E_kin_free_energy'), bu.Item('E_plastic_diss'), ) WUG_t = tr.Property def _get_W_t(self): W_arr = ( cumtrapz(self.Sig_arr[:, 0], self.s_x_t, initial=0) + cumtrapz(self.Sig_arr[:, 1], self.s_y_t, initial=0) + cumtrapz(self.Sig_arr[:, 2], self.w_t, initial=0) ) s_x_el_t = (self.s_x_t - self.Eps_arr[:, 0]) s_y_el_t = (self.s_y_t - self.Eps_arr[:, 1]) w_el_t = (self.w_t - self.Eps_arr[:, 2]) U_arr = ( self.Sig_arr[:, 0] * s_x_el_t / 2.0 + self.Sig_arr[:, 1] * s_y_el_t / 2.0 + self.Sig_arr[:, 2] * w_el_t / 2.0 ) G_arr = W_arr - U_arr return W_arr, U_arr, G_arr Eps = tr.Property """Energy dissipated in associatiation with individual internal variables """ def _get_Eps(self): Eps_names = self.slider_exp.slide_model.Eps_names E_i = cumtrapz(self.Sig_arr, self.Eps_arr, initial=0, axis=0) return SimpleNamespace(**{Eps_name: E for Eps_name, E in zip(Eps_names, E_i.T)}) mechanisms = tr.Property """Energy in association with mechanisms (damage and plastic dissipation) or free energy """ def _get_mechanisms(self): E_i = cumtrapz(self.Sig_arr, self.Eps_arr, initial=0, axis=0) E_T_x_pi_, E_T_y_pi_, E_N_pi_, E_z_, E_alpha_x_, E_alpha_y_, E_omega_T_, E_omega_N_ = E_i.T E_plastic_work_T = E_T_x_pi_ + E_T_y_pi_ E_plastic_work_N = E_N_pi_ E_plastic_work = E_plastic_work_T + E_plastic_work_N E_iso_free_energy = E_z_ E_kin_free_energy = E_alpha_x_ + E_alpha_y_ E_plastic_diss_T = E_plastic_work_T - E_iso_free_energy - E_kin_free_energy E_plastic_diss_N = E_plastic_work_N E_plastic_diss = E_plastic_diss_T + E_plastic_diss_N E_damage_diss = E_omega_T_ + E_omega_N_ return SimpleNamespace(**{'plastic_work_N': E_plastic_work_N, 'plastic_work_T': E_plastic_work_T, 'plastic_work': E_plastic_work, 'iso_free_energy': E_iso_free_energy, 'kin_free_energy': E_kin_free_energy, 'plastic_diss_N': E_plastic_diss_N, 'plastic_diss_T': E_plastic_diss_T, 'plastic_diss': E_plastic_diss, 'damage_diss_N': E_omega_N_, 'damage_diss_T': E_omega_T_, 'damage_diss': E_damage_diss}) def plot_energy(self, ax, ax_i): W_arr = ( cumtrapz(self.Sig_arr[:, 0], self.s_x_t, initial=0) + cumtrapz(self.Sig_arr[:, 1], self.s_y_t, initial=0) + cumtrapz(self.Sig_arr[:, 2], self.w_t, initial=0) ) s_x_el_t = (self.s_x_t - self.Eps_arr[:, 0]) s_y_el_t = (self.s_y_t - self.Eps_arr[:, 1]) w_el_t = (self.w_t - self.Eps_arr[:, 2]) U_arr = ( self.Sig_arr[:, 0] * s_x_el_t / 2.0 + self.Sig_arr[:, 1] * s_y_el_t / 2.0 + self.Sig_arr[:, 2] * w_el_t / 2.0 ) G_arr = W_arr - U_arr ax.plot(self.t_arr, W_arr, lw=0.5, color='black', label=r'$W$ - Input work') ax.plot(self.t_arr, G_arr, '--', color='black', lw = 0.5, label=r'$W^\mathrm{inel}$ - Inelastic work') ax.fill_between(self.t_arr, W_arr, G_arr, color=self.colors['stored_energy'], alpha=0.2) ax.set_xlabel('$t$ [-]'); ax.set_ylabel(r'$E$ [Nmm]') ax.legend() E_i = cumtrapz(self.Sig_arr, self.Eps_arr, initial=0, axis=0) E_T_x_pi_, E_T_y_pi_, E_N_pi_, E_z_, E_alpha_x_, E_alpha_y_, E_omega_T_, E_omega_N_ = E_i.T E_plastic_work_T = E_T_x_pi_ + E_T_y_pi_ E_plastic_work_N = E_N_pi_ E_plastic_work = E_plastic_work_T + E_plastic_work_N E_iso_free_energy = E_z_ E_kin_free_energy = E_alpha_x_ + E_alpha_y_ E_plastic_diss_T = E_plastic_work_T - E_iso_free_energy - E_kin_free_energy E_plastic_diss_N = E_plastic_work_N E_plastic_diss = E_plastic_diss_T + E_plastic_diss_N E_damage_diss = E_omega_T_ + E_omega_N_ E_level = 0 if self.E_damage_diss: ax.plot(self.t_arr, E_damage_diss + E_level, color='black', lw=1) ax_i.plot(self.t_arr, E_damage_diss, color='gray', lw=2, label=r'damage diss.: $Y\dot{\omega}$') ax.fill_between(self.t_arr, E_omega_N_ + E_level, E_level, color='black', hatch='|'); E_d_level = E_level + E_omega_N_ ax.fill_between(self.t_arr, E_omega_T_ + E_d_level, E_d_level, color='gray', alpha=0.3); E_level = E_damage_diss if self.E_plastic_work: ax.plot(self.t_arr, E_plastic_work + E_level, lw=0.5, color='black') # ax.fill_between(self.t_arr, E_plastic_work + E_level, E_level, color='red', alpha=0.3) label = r'plastic work: $\sigma \dot{\varepsilon}^\pi$' ax_i.plot(self.t_arr, E_plastic_work, color='red', lw=2,label=label) ax.fill_between(self.t_arr, E_plastic_work_N + E_level, E_level, color='orange', alpha=0.3); E_p_level = E_level + E_plastic_work_N ax.fill_between(self.t_arr, E_plastic_work_T + E_p_level, E_p_level, color='red', alpha=0.3); if self.E_plastic_diss: ax.plot(self.t_arr, E_plastic_diss + E_level, lw=.4, color='black') label = r'apparent pl. diss.: $\sigma \dot{\varepsilon}^\pi - X\dot{\alpha} - Z\dot{z}$' ax_i.plot(self.t_arr, E_plastic_diss, color='red', lw=2, label=label) ax.fill_between(self.t_arr, E_plastic_diss_N + E_level, E_level, color='red', hatch='-'); E_d_level = E_level + E_plastic_diss_N ax.fill_between(self.t_arr, E_plastic_diss_T + E_d_level, E_d_level, color='red', alpha=0.3); E_level += E_plastic_diss if self.E_iso_free_energy: ax.plot(self.t_arr, E_iso_free_energy + E_level, '-.', lw=0.5, color='black') ax.fill_between(self.t_arr, E_iso_free_energy + E_level, E_level, color='royalblue', hatch='|') ax_i.plot(self.t_arr, -E_iso_free_energy, '-.', color='royalblue', lw=2, label=r'iso. diss.: $Z\dot{z}$') E_level += E_iso_free_energy if self.E_kin_free_energy: ax.plot(self.t_arr, E_kin_free_energy + E_level, '-.', color='black', lw=0.5) ax.fill_between(self.t_arr, E_kin_free_energy + E_level, E_level, color='royalblue', alpha=0.2); ax_i.plot(self.t_arr, -E_kin_free_energy, '-.', color='blue', lw=2, label=r'free energy: $X\dot{\alpha}$') ax_i.legend() ax_i.set_xlabel('$t$ [-]'); ax_i.set_ylabel(r'$E$ [Nmm]') @staticmethod def subplots(fig): ax_work, ax_energies = fig.subplots(1, 2) ax_iter = ax_work.twinx() return ax_work, ax_energies, ax_iter def update_plot(self, axes): ax_work, ax_energies, ax_iter = axes self.plot_energy(ax_work, ax_energies) if self.show_iter: ax_iter.plot(self.t_arr, self.iter_t) ax_iter.set_ylabel(r'$n_\mathrm{iter}$') def xsubplots(self, fig): ((ax1, ax2), (ax3, ax4)) = fig.subplots(2, 2, figsize=(10, 5), tight_layout=True) ax11 = ax1.twinx() ax22 = ax2.twinx() ax33 = ax3.twinx() ax44 = ax4.twinx() return ax1, ax11, ax2, ax22, ax3, ax33, ax4, ax44 def xupdate_plot(self, axes): ax1, ax11, ax2, ax22, ax3, ax33, ax4, ax44 = axes self.get_response([6, 0, 0]) # plot_Sig_Eps(s_x_t, Sig_arr, Eps_arr, iter_t, *axes) s_x_pi_, s_y_pi_, w_pi_, z_, alpha_x_, alpha_y_, omega_s_, omega_w_ = self.Eps_arr.T tau_x_pi_, tau_y_pi_, sig_pi_, Z_, X_x_, X_y_, Y_s_, Y_w_ = self.Sig_arr.T ax1.plot(self.w_t, sig_pi_, color='green') ax11.plot(self.s_x_t, tau_x_pi_, color='red') ax2.plot(self.w_t, omega_w_, color='green') ax22.plot(self.w_t, omega_s_, color='red')
class TStep(tr.HasStrictTraits): model = tr.Instance(IModel) sim = tr.DelegatesTo('model') t_n = tr.Float(0.0) t_n1 = tr.Float(0.0) def init_state(self): self.t_n = 0 self.t_n1 = 0 self.model.init_state() self.linalg_sys.register_constraints(0, 1) for var, state in self.model.S.items(): state[...] = 0 trial_state_changed = tr.Event R_dR_dU = tr.Property(depends_on='trial_state_changed, t_n1') @tr.cached_property def _get_R_dR_dU(self): R = self.R dR_dU = self.dR_dU return R, dR_dU R = tr.Property(depends_on='trial_state_changed, t_n1') @tr.cached_property def _get_R(self): bc = self.model.bc R = self.model.F - self.t_n1 return R R_norm = tr.Property(depends_on='trial_state_changed, t_n1') @tr.cached_property def _get_R_norm(self): R = self.R return np.sqrt(np.einsum('...i,...i', R, R)) dR_dU = tr.Property(depends_on='trial_state_changed') @tr.cached_property def _get_dR_dU(self): return self.model.d_F_U linalg_sys = tr.Instance(LinAlgSys, ()) def make_iter(self): R, dR_dU = self.R_dR_dU self.linalg_sys.A = dR_dU self.linalg_sys.b_0 = R self.linalg_sys.apply_constraints( self.step_flag, self.t_n, self.t_n1 ) d_U = self.linalg_sys.solve() #d_U = -R / dR_dU self.model.U_k += d_U self.trial_state_changed = True def make_incr(self, t_n1): self.model.U_n[...] = self.model.U_k self.t_n1 = t_n1
class FETriangularMesh(bu.Model): name = 'FETriangularMesh' X_Id = tr.Array(np.float_, value=[[0, 0, 0], [2, 0, 0], [2, 2, 0], [1, 1, 0]]) I_Fi = tr.Array(np.int_, value=[ [0, 1, 3], [1, 2, 3], ]) fets = tr.Instance(FETSEval) def _fets_default(self): return FETS2D3U1M() show_node_labels = bu.Bool(False) n_nodal_dofs = tr.DelegatesTo('fets') dof_offset = tr.Int(0) n_active_elems = tr.Property def _get_n_active_elems(self): return len(self.I_Fi) ipw_view = bu.View(bu.Item('show_node_labels'), ) #========================================================================= # 3d Visualization #========================================================================= plot_backend = 'k3d' show_wireframe = bu.Bool(True) def setup_plot(self, pb): X_Id = self.X_Id.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) fe_mesh = k3d.mesh(X_Id, I_Fi, color=0x999999, opacity=1.0, side='double') pb.plot_fig += fe_mesh pb.objects['mesh'] = fe_mesh if self.show_wireframe: k3d_mesh_wireframe = k3d.mesh(X_Id, I_Fi, color=0x000000, wireframe=True) pb.plot_fig += k3d_mesh_wireframe pb.objects['mesh_wireframe'] = k3d_mesh_wireframe if self.show_node_labels: self._add_nodes_labels_to_fig(pb, X_Id) NODES_LABELS = 'nodes_labels' def update_plot(self, pb): X_Id = self.X_Id.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) mesh = pb.objects['mesh'] mesh.vertices = X_Id mesh.indices = I_Fi if self.show_wireframe: wireframe = pb.objects['mesh_wireframe'] wireframe.vertices = X_Id wireframe.indices = I_Fi 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_Id) else: if self.NODES_LABELS in pb.objects: pb.clear_object(self.NODES_LABELS) def _add_nodes_labels_to_fig(self, pb, X_Id): text_list = [] for I, X_d in enumerate(X_Id): k3d_text = k3d.text('%g' % I, tuple(X_d), 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
class TLoop(tr.HasTraits): tstep = tr.Instance(ITStep) sim = tr.DelegatesTo('tstep') tline = tr.Property def _get_tline(self): return self.sim.tline k_max = tr.Int(100, enter_set=True, auto_set=False) acc = tr.Float(1e-4, enter_set=True, auto_set=False) verbose = tr.Bool(False, enter_set=True, auto_set=False) paused = tr.Bool(False) restart = tr.Bool(True) user_wants_abort = tr.Property def _get_user_wants_abort(self): return self.restart or self.paused def init(self): if self.paused: self.paused = False if self.restart: self.tline.val = self.tline.min self.tstep.init_state() self.restart = False def eval(self): t_n1 = self.tline.val t_max = self.tline.max dt = self.tline.step if self.verbose: print('t:', end='') while t_n1 <= (t_max + 1e-8): if self.verbose: print('\t%5.2f' % t_n1, end='') k = 0 self.tstep.t_n1 = t_n1 while (k < self.k_max) and (not self.user_wants_abort): R_norm = self.tstep.R_norm if R_norm < self.acc: if self.verbose: print('(%g), ' % k, end='\n') break try: self.tstep.make_iter() except RuntimeError as e: raise(e) k += 1 else: # handle unfinished iteration loop if k >= self.k_max: # maximum number of restarts exceeded # no success abort the simulation self.restart = True print('') raise StopIteration('Warning: ' 'convergence not reached in %g iterations' % k) else: # reduce the step size dt /= 2 continue # accept the time step and record the state in history self.tstep.make_incr(t_n1) # update the line - launches notifiers to subscribers self.tline.val = min(t_n1, self.tline.max) # set a new target time t_n1 += dt self.tstep.t_n1 = t_n1 return