class CustomShape(CrossSectionShapeBase): cs_points_str = Str( '100, 0\n100, 140\n25, 150\n25, 750\n100, 760\n100, 900\n-100, 900\n-100, 760\n-25, 750\n-25, 150\n-100, 140\n-100, 0' ) apply_points_btn = Button() @tr.observe("apply_points_btn") def apply_points_btn_clicked(self, event): print( 'This should update the plot with the new points, but maybe it\'s not needed' ) ipw_view = View( Item('cs_points_str', latex=r'\mathrm{Points}', editor=TextAreaEditor()), Item('apply_points_btn', editor=ButtonEditor(label='Apply points', icon='refresh')), ) cs_points = tr.Property() def _get_cs_points(self): return self._parse_2d_points_str_into_array(self.cs_points_str) @staticmethod def _parse_2d_points_str_into_array(points_str): """ This will parse str of points written like this '0, 0\n 1, 1' to ([0, 0], [1, 1]) """ points_str = points_str.replace('\n', ', ').replace('\r', ', ') points_array = np.fromstring(points_str, dtype=int, sep=',') if points_array.size % 2 != 0: points_array = np.append(points_array, 0) points_array = points_array.reshape((int(points_array.size / 2), 2)) return points_array.reshape((int(points_array.size / 2), 2)) def get_cs_area(self): points_xy = self.cs_points x = points_xy[:, 0] y = points_xy[:, 1] # See https://stackoverflow.com/a/30408825 for following Implementation of Shoelace formula return 0.5 * np.abs( np.dot(x, np.roll(y, 1)) - np.dot(y, np.roll(x, 1))) def get_cs_i(self): pass def get_b(self, z_positions_array): # TODO get b for polygon given its points coordinates pass def update_plot(self, ax): cs = Polygon(self.cs_points) patch_collection = PatchCollection([cs], facecolor=(.5, .5, .5, 0.2), edgecolors=(0, 0, 0, 1)) ax.add_collection(patch_collection) # ax.scatter(0, CustomShape.get_cs_i(self)[0], color='white', s=self.B_w, marker="+") ax.autoscale_view() ax.set_aspect('equal')
class CarbonReinfMatMod(ReinfMatMod, bu.InjectSymbExpr): name = 'Carbon' symb_class = CarbonReinfMatModSymbExpr E = bu.Float(200000, MAT=True, desc='E modulus of carbon') f_t = bu.Float(2000, MAT=True, desc='carbon breaking stress') eps_cr = tr.Property(bu.Float, depends_on='+MAT') @tr.cached_property def _get_eps_cr(self): return self.f_t / self.E ipw_view = bu.View( bu.Item('factor'), bu.Item('E', latex=r'E \mathrm{[N/mm^{2}]}'), bu.Item('f_t', latex=r'f_\mathrm{t} \mathrm{[N/mm^{2}]}'), bu.Item('eps_cr', latex=r'\varepsilon_\mathrm{cr} \mathrm{[-]}'), ) def get_eps_plot_range(self): return np.linspace(-0.1 * self.eps_cr, 1.1 * self.eps_cr, 300) def get_sig(self, eps): # TODO: factor should be applied only to strength in case of steel/carbon according to EC2 temp = self.f_t self.f_t *= self.factor sig = self.symb.get_sig(eps) self.f_t = temp return sig
class SteelReinfMatMod(ReinfMatMod, bu.InjectSymbExpr): name = 'Steel' symb_class = SteelReinfMatModSymbExpr E_s = bu.Float(200000, MAT=True, desc='E modulus of steel') f_sy = bu.Float(500, MAT=True, desc='steel yield stress') eps_ud = bu.Float(0.025, MAT=True, desc='steel failure strain') eps_sy = tr.Property(bu.Float, depends_on='+MAT') @tr.cached_property def _get_eps_sy(self): return self.f_sy / self.E_s ipw_view = bu.View( bu.Item('factor'), bu.Item('E_s', latex=r'E_\mathrm{s} \mathrm{[N/mm^{2}]}'), bu.Item('f_sy', latex=r'f_\mathrm{sy} \mathrm{[N/mm^{2}]}'), bu.Item('eps_ud', latex=r'\varepsilon_\mathrm{ud} \mathrm{[-]}'), bu.Item('eps_sy', latex=r'\varepsilon_\mathrm{sy} \mathrm{[-]}', readonly=True), ) def get_eps_plot_range(self): return np.linspace(-1.1 * self.eps_ud, 1.1 * self.eps_ud, 300) def get_sig(self, eps): temp = self.f_sy self.f_sy *= self.factor sig = self.symb.get_sig(eps) self.f_sy = temp return sig
class FormingProcessView(tr.HasStrictTraits): '''Forming process viewer with task tree editor ''' forming_process = tr.Instance(FormingProcess) root = tr.Property(tr.Instance(FormingTask)) '''All FormingTask steps. ''' def _get_root(self): return self.forming_process.factory_task selected = tr.Instance(IFormingTask) view1 = View(VSplit(Group( Item('root', editor=FormingTask_tree_editor, resizable=True, show_label=False), label='Design tree', scrollable=True, ), Group(UItem('selected@')), dock='tab'), dock='tab', resizable=True, title='Forming Process View', width=1.0, height=1.0)
class CrossSection(BMCSLeafNode, RInputRecord): '''Parameters of the pull-out cross section ''' node_name = 'cross-section' R_m = tr.Float(20, CS=True, input=True, unit=r'$\mathrm{mm}$', symbol=r'R_\mathrm{m}', auto_set=False, enter_set=True, desc='matrix area') R_f = tr.Float(1.0, CS=True, input=True, unit='$\\mathrm{mm}$', symbol='R_\mathrm{f}', auto_set=False, enter_set=True, desc='reinforcement area') P_b = tr.Property(unit='$\\mathrm{mm}$', symbol='p_\mathrm{b}', desc='perimeter of the bond interface', depends_on='R_f') @tr.cached_property def _get_P_b(self): return 2 * np.pi * self.R_f view = ui.View(ui.Item('R_m'), ui.Item('R_f'), ui.Item('P_b', style='readonly')) tree_view = view
class CrossSectionDesign(Model): name = 'Cross Section Design' matrix = EitherType( options=[ ('piecewise linear', PWLConcreteMatMod), ('EC2', EC2ConcreteMatMod), ('EC2 with plateau', EC2PlateauConcreteMatMod), # ('EC2 softening tension', ConcreteMaterialModelAdv), ], MAT=True) cross_section_layout = Instance(CrossSectionLayout) def _cross_section_layout_default(self): return CrossSectionLayout(cs_design=self) tree = ['matrix', 'cross_section_layout', 'cross_section_shape'] csl = tr.Property def _get_csl(self): return self.cross_section_layout H = tr.Property(Float) def _get_H(self): return self.cross_section_shape_.H def _set_H(self, value): self.cross_section_shape_.H = value cross_section_shape = EitherType( options=[ ('rectangle', Rectangle), # ('circle', Circle), ('I-shape', IShape), ('T-shape', TShape), ('custom', CustomShape) ], CS=True, tree=True) ipw_view = View( Item('matrix', latex=r'\mathrm{concrete behavior}', editor=EitherTypeEditor(show_properties=False)), Item('cross_section_shape', latex=r'\mathrm{shape}', editor=EitherTypeEditor(show_properties=False)), Item('cross_section_layout', latex=r'\mathrm{layout}'), ) def subplots(self, fig): return fig.subplots(1, 1) def update_plot(self, ax): self.cross_section_shape_.update_plot(ax) self.cross_section_layout.update_plot(ax)
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 Geo(tr.HasStrictTraits): eta = tr.List([eta_0, eta_1, eta_2]) R_r = tr.List([sp.sin(eta_0) * eta_0, sp.cos(eta_0 + 1)**2, eta_1]) def F_r(self, *eta_): r = np.array([ re.subs({e: et for e, et in zip(self.eta, eta_)}) for re in self.R_r ], dtype=np.float_) return np.einsum('ij,jk->ki', r[:, np.newaxis], np.identity(3)) M_ra = tr.Property(depends_on='R_r, R_r_items') @tr.cached_property def _get_M_ra(self): return [[sp.diff(r, e) for r in self.R_r] for e in self.eta] def F_m(self, *eta_): return np.array( [[m.subs({e: et for e, et in zip(self.eta, eta_)}) for m in m_a] for m_a in self.M_ra], dtype=np.float_) def orthogonal_base(self, *eta_): m_01 = self.F_m(*eta_) m_2 = np.einsum('...i,...j,ijk->...k', m_01[0, :], m_01[1, :], EPS) m_1a = np.einsum('...i,...j,ijk->...k', m_2, m_01[0, :], EPS) m = np.array([m_01[0, :], m_1a, m_2]) m1 = m / np.sqrt(np.einsum('ij,ij->i', m, m))[:, np.newaxis] return m1 def plot_basis(self, *eta): r = self.F_r(*eta) m = self.orthogonal_base(*eta) rm = r + m mp = np.vstack([r[np.newaxis, ...], rm[np.newaxis, ...]]) mlab.plot3d(mp[:, 0, 0], mp[:, 0, 1], mp[:, 0, 2], color=black, tube_radius=0.01) mlab.plot3d(mp[:, 1, 0], mp[:, 1, 1], mp[:, 1, 2], color=black, tube_radius=0.01) mlab.plot3d(mp[:, 2, 0], mp[:, 2, 1], mp[:, 2, 2], color=black, tube_radius=0.01) mlab.text3d(rm[0, 0], rm[0, 1], rm[0, 2], 'X', color=black, scale=0.1) mlab.text3d(rm[1, 0], rm[1, 1], rm[1, 2], 'Y', color=black, scale=0.1) mlab.text3d(rm[2, 0], rm[2, 1], rm[2, 2], 'Z', color=black, scale=0.1)
class EC2PlateauConcreteMatMod(EC2ConcreteMatModBase, bu.InjectSymbExpr): name = 'EC2 Concrete with Plateau' symb_class = EC2PlateauConcreteMatModSymbExpr f_cm = bu.Float(28) f_cd = tr.Property(desc='Design compressive strength of concrete', MAT=True) def _get_f_cd(self): if self.factor == 1: return self.f_cm else: return EC2.get_f_cd(self.f_ck, factor=self.factor) f_ck = tr.Property(desc='Characteristic compressive strength of concrete', MAT=True) def _get_f_ck(self): return EC2.get_f_ck_from_f_cm(self.f_cm) n = tr.Property(desc='Exponent used in EC2, eq. 3.17', MAT=True) def _get_n(self): return EC2.get_n(self.f_ck) eps_cy = tr.Property(desc='Matrix compressive yield strain', MAT=True) def _get_eps_cy(self): return -EC2.get_eps_c2(self.f_ck) eps_cu = tr.Property(desc='Ultimate matrix compressive strain', MAT=True) def _get_eps_cu(self): return -EC2.get_eps_cu2(self.f_ck) def get_sig(self, eps): sig = self.symb.get_sig(eps) # Compression branch is scaled when defining f_cd sig_with_scaled_tension_branch = np.where(sig > 0, self.factor * sig, sig) return sig_with_scaled_tension_branch ipw_view = bu.View(*EC2ConcreteMatModBase.ipw_view.content, )
class DescStatBasePlot(BasePlot): ds = _traits.Instance(DataSet) plot_data = _traits.Property() """The data set that is to be shown i table view of the plot data""" def _get_plot_data(self): nds = copy.deepcopy(self.ds) df = self.ds.mat.transpose() nds.mat = df.sort_index(axis=0, ascending=False) return nds
class Model(BMCSTreeNode): '''Contains the primary unknowns variables U_k ''' tstep_type = tr.Type(TStep) tloop_type = tr.Type(TLoop) hist_type = tr.Type(Hist) sim_type = tr.Type(SimControler) tstep = tr.Property(depends_on='tstep_type') @tr.cached_property def _get_tstep(self): return self.tstep_type(model=self) tloop = tr.Property(depends_on='tloop_type') @tr.cached_property def _get_tloop(self): return self.tloop_type(tstep=self.tstep) hist = tr.Property(depends_on='hist_type') @tr.cached_property def _get_hist(self): return self.hist_type(model=self) sim = tr.Property(depends_on='sim_type') @tr.cached_property def _get_sim(self): return self.sim_type(model=self) bc = tr.List(tr.Callable) U_shape = tr.Tuple(1,) def init_state(self): self.U_k = np.zeros(self.U_shape, dtype=np.float) self.U_n = np.copy(self.U_n) self.hist.init_state() def get_plot_sheet(self): return U_k = tr.Array(np.float_, TRIAL_STATE=True) U_n = tr.Array(np.float_, FUND_STATE=True) S = tr.Dict(tr.Str, tr.Array(np.float), STATE=True) F = tr.Property(depends_on='+TRIAL_STATE,+INPUT') @tr.cached_property def _get_F(self): raise NotImplemented d_F_U = tr.Property(depends_on='+TRIAL_STATE,+INPUT') @tr.cached_property def _get_d_F_U(self): raise NotImplemented
class Viz3D(tr.HasTraits): vis3d = tr.WeakRef name = tr.Property() def _get_name(self): return self.vis3d.var def setup(self): pass def plot(self, vot): pass
class TimeFunction(bu.InteractiveModel): name = 'Time function' n_i = bu.Int(0, input=True, desc='number of data points between data points') '''Number of steps between two values ''' values = tr.Array(np.float_, value=[0, 1]) '''Values of the time function. ''' ipw_view = bu.View(bu.Item('n_i', latex=r'n_i', minmax=(0, 100))) t_step = tr.Property(tr.Float, depends_on='+input') '''Step size. ''' @tr.cached_property def _get_t_step(self): n_values = len(self.values) return (self.n_i + 1) * n_values tf = tr.Property(depends_on='data_points') def _get_tf(self): n_values = len(self.values) t_values = np.linspace(0, 1, n_values) return interpolate.interp1d(t_values, self.values) def __call__(self, arg): return self.tf(arg) def update_plot(self, ax): n_values = len(self.values) t = np.linspace(0, 1, (self.n_i) * (n_values - 1) + n_values) v = self.tf(t) ax.plot(t, v, '-o')
class MSIntegScheme(Model): """Interface of an integration scheme """ MPNN = tr.Property(depends_on='n_mp') """Get the operator of the microplane normals """ @tr.cached_property def _get_MPNN(self): MPNN_nij = np.einsum('ni,nj->nij', self.MPN, self.MPN) return MPNN_nij MPTT = tr.Property(depends_on='n_mp') """Rank three tangential tensor (operator) for each microplane """ @tr.cached_property def _get_MPTT(self): delta = np.identity(3) MPTT_nijr = 0.5 * ( np.einsum('ni,jr -> nijr', self.MPN, delta) + np.einsum('nj,ir -> njir', self.MPN, delta) - 2 * np.einsum('ni,nj,nr -> nijr', self.MPN, self.MPN, self.MPN)) return MPTT_nijr
class Viz3DField(Viz3D): visible = tr.Property(tr.Bool) def _set_visible(self, visible): self.d.visible = visible def _get_visible(self): return self.d.visible def plot(self, vot): # self.vis3d.sim ts = self.vis3d.tstep idx = ts.hist.get_time_idx(vot) self.d.file_list = self.vis3d.file_list self.d.timestep = idx
class HCFFRoot(HCFFParent): #name = tr.Str('root') import_manager = tr.Instance(FileImportManager, ()) plot_options = tr.List(PlotOptions) output_npy = tr.Property() tree_view = ui.View( ui.VGroup( ui.Item('import_manager', style='custom', show_label=False) ) ) traits_view = tree_view
class BasePlot(_chaco.DataView): # Copy from chaco.plot # The title of the plot. title = _traits.Property() # Use delegates to expose the other PlotLabel attributes of the plot title title_text = _traits.Delegate("_title", prefix="text", modify=True) # The PlotLabel object that contains the title. _title = _traits.Instance(_chaco.PlotLabel) def __init__(self, *args, **kwargs): super(BasePlot, self).__init__(*args, **kwargs) self._init_title() def _init_title(self): self._title = _chaco.PlotLabel(font="swiss 16", visible=False, overlay_position="top", component=self) def __title_changed(self, old, new): self._overlay_change_helper(old, new) def _set_title(self, text): self._title.text = text if text.strip() != "": self._title.visible = True else: self._title.visible = False def _get_title(self): return self._title.text def get_plot_name(self): return self.plot_data.display_name def export_image(self, fname, size=sz_plot_img): """Save plot as png image.""" # self.outer_bounds = list(size) # self.do_layout(force=True) gc = _chaco.PlotGraphicsContext(self.outer_bounds) gc.render_component(self) gc.save(fname, file_format=None)
class PointCloud(Vis3D): '''State object ''' p = tr.Tuple '''Point positions ''' def _p_default(self): x, y, z, s = np.random.random((4, 100)) return x, y, z, s points = tr.Property(tr.Tuple) def _get_points(self): x, y, z, s = self.p c = self.vot return x * c, y * c, z * c, s * c viz3d_classes = dict(default=PointCloudViz3D, something_else=PointCloudViz3D)
class TimeFunction(Model): name = 'time function' t_max = Float(1.0, TIME=True) t_shift = Float(0.0, TIME=True) def __call__(self, arg): return self.time_function(arg) time_function = tr.Property(depends_on='state_changed') @tr.cached_property def _get_time_function(self): return self._generate_time_function() def update_plot(self, axes): t_range = np.linspace(0, self.t_max, 500) f_range = self.time_function(t_range) axes.plot(t_range, f_range) axes.set_xlabel(r'$t$ [-]') axes.set_ylabel(r'$\theta$ [-]')
class PlotBase(_chaco.Plot): # The data set to return when plot window asks for the data to # show in table form # I have to override this property as well when i override the getter plot_data = _traits.Property() # The function to call if the plot is double clicked ld_action_func = _traits.Callable() # Add extra room for y axis padding_left = 75 def add_left_down_action(self, left_down_action_func): self.ld_action_func = left_down_action_func def normal_left_down(self, mouse_event): if self.ld_action_func: self.ld_action_func() def _get_plot_data(self): raise NotImplementedError('plot_data getter is not implemented') def get_plot_name(self): return self.plot_data.display_name
class TFSelector(TimeFunction): name = 'time function' profile = EitherType(options=[ ('monotonic', TFMonotonic), ('bilinear', TFBilinear), ('cyclic-sym-incr', TFCyclicSymmetricConstant), ('cyclic-sym-const', TFCyclicSymmetricIncreasing), ('cyclic-nonsym-incr', TFCyclicNonsymmetricConstant), ('cyclic-nonsym-const', TFCyclicNonsymmetricIncreasing) ], TIME=True) t_max = tr.Property(Float) def _get_t_max(self): return self.profile_.t_max ipw_view = View(Item('profile')) def __call__(self, arg): return self.profile_(arg) def update_plot(self, axes): self.profile_.update_plot(axes)
class WBTessellation4P(bu.Model): name = 'WB Tessellation 4P' wb_cell = bu.Instance(WBCell4Param) def _wb_cell_default(self): wb_cell = WBCell4Param() self.update_wb_cell_params(wb_cell) return wb_cell tree = ['wb_cell'] plot_backend = 'k3d' n_phi_plus = bu.Int(5, GEO=True) n_x_plus = bu.Int(3, GEO=True) gamma = bu.Float(1.25, GEO=True) a = bu.Float(1000, GEO=True) a_high = bu.Float(2000) b = bu.Float(1000, GEO=True) b_high = bu.Float(2000) c = bu.Float(1000, GEO=True) c_high = bu.Float(2000) show_wireframe = bu.Bool(True, GEO=True) show_nodes = bu.Bool(False, GEO=True) show_node_labels = bu.Bool(False, GEO=True) WIREFRAME = 'k3d_mesh_wireframe' NODES = 'k3d_nodes' NODES_LABELS = 'k3d_nodes_labels' @tr.observe('+GEO', post_init=True) def update_wb_cell(self, event): self.update_wb_cell_params(self.wb_cell) def update_wb_cell_params(self, wb_cell): wb_cell.trait_set( gamma=self.gamma, a=self.a, a_high=self.a_high, b=self.b, b_high=self.b_high, c=self.c, c_high=self.c_high, ) ipw_view = bu.View( # bu.Item('wb_cell'), *WBCell4Param.ipw_view.content, bu.Item('n_phi_plus', latex=r'n_\phi'), bu.Item('n_x_plus', latex=r'n_x'), # bu.Item('show_wireframe'), # bu.Item('show_node_labels'), bu.Item('show_nodes'), ) def get_phi_range(self, delta_phi): return np.arange(-(self.n_phi_plus - 1), self.n_phi_plus) * delta_phi def get_X_phi_range(self, delta_phi, R_0): """Given an array of angles and radius return an array of coordinates """ phi_range = self.get_phi_range((delta_phi)) return np.array([ np.fabs(R_0) * np.sin(phi_range), np.fabs(R_0) * np.cos(phi_range) + R_0 ]).T def get_X_x_range(self, delta_x): return np.arange(-(self.n_x_plus - 1), self.n_x_plus) * delta_x cell_map = tr.Property def _get_cell_map(self): delta_x = self.wb_cell.delta_x delta_phi = self.wb_cell.delta_phi R_0 = self.wb_cell.R_0 X_x_range = self.get_X_x_range(delta_x) X_phi_range = self.get_X_phi_range(delta_phi, R_0) n_idx_x = len(X_x_range) n_idx_phi = len(X_phi_range) idx_x = np.arange(n_idx_x) idx_phi = np.arange(n_idx_phi) idx_x_ic = idx_x[(n_idx_x) % 2::2] idx_x_id = idx_x[(n_idx_x + 1) % 2::2] idx_phi_ic = idx_phi[(n_idx_phi) % 2::2] idx_phi_id = idx_phi[(n_idx_phi + 1) % 2::2] n_ic = len(idx_x_ic) * len(idx_phi_ic) n_id = len(idx_x_id) * len(idx_phi_id) n_cells = n_ic + n_id return n_cells, n_ic, n_id, idx_x_ic, idx_x_id, idx_phi_ic, idx_phi_id n_cells = tr.Property def _get_n_cells(self): n_cells, _, _, _, _, _, _ = self.cell_map return n_cells X_cells_Ia = tr.Property(depends_on='+GEO') '''Array with nodal coordinates of uncoupled cells I - node, a - dimension ''' @tr.cached_property def _get_X_cells_Ia(self): delta_x = self.wb_cell.delta_x delta_phi = self.wb_cell.delta_phi R_0 = self.wb_cell.R_0 X_Ia_wb_rot = np.copy(self.wb_cell.X_Ia) X_Ia_wb_rot[..., 2] -= R_0 X_cIa = np.array([X_Ia_wb_rot], dtype=np.float_) rotation_axes = np.array([[1, 0, 0]], dtype=np.float_) rotation_angles = self.get_phi_range(delta_phi) q = axis_angle_to_q(rotation_axes, rotation_angles) X_dIa = qv_mult(q, X_cIa) X_dIa[..., 2] += R_0 X_x_range = self.get_X_x_range(delta_x) X_phi_range = self.get_X_phi_range(delta_phi, R_0) n_idx_x = len(X_x_range) n_idx_phi = len(X_phi_range) idx_x = np.arange(n_idx_x) idx_phi = np.arange(n_idx_phi) idx_x_ic = idx_x[(n_idx_x) % 2::2] idx_x_id = idx_x[(n_idx_x + 1) % 2::2] idx_phi_ic = idx_phi[(n_idx_phi) % 2::2] idx_phi_id = idx_phi[(n_idx_phi + 1) % 2::2] X_E = X_x_range[idx_x_ic] X_F = X_x_range[idx_x_id] X_CIa = X_dIa[idx_phi_ic] X_DIa = X_dIa[idx_phi_id] expand = np.array([1, 0, 0]) X_E_a = np.einsum('i,j->ij', X_E, expand) X_ECIa = X_CIa[np.newaxis, :, :, :] + X_E_a[:, np.newaxis, np.newaxis, :] X_F_a = np.einsum('i,j->ij', X_F, expand) X_FDIa = X_DIa[np.newaxis, :, :, :] + X_F_a[:, np.newaxis, np.newaxis, :] X_Ia = np.vstack( [X_ECIa.flatten().reshape(-1, 3), X_FDIa.flatten().reshape(-1, 3)]) return X_Ia I_cells_Fi = tr.Property(depends_on='+GEO') '''Array with nodal coordinates I - node, a - dimension ''' @tr.cached_property def _get_I_cells_Fi(self): I_Fi_cell = self.wb_cell.I_Fi n_I_cell = self.wb_cell.n_I n_cells = self.n_cells i_range = np.arange(n_cells) * n_I_cell I_Fi = (I_Fi_cell[np.newaxis, :, :] + i_range[:, np.newaxis, np.newaxis]).reshape(-1, 3) return I_Fi X_Ia = tr.Property(depends_on='+GEO') '''Array with nodal coordinates I - node, a - dimension ''' @tr.cached_property def _get_X_Ia(self): idx_unique, idx_remap = self.unique_node_map return self.X_cells_Ia[idx_unique] I_Fi = tr.Property(depends_on='+GEO') '''Facet - node mapping ''' @tr.cached_property def _get_I_Fi(self): _, idx_remap = self.unique_node_map return idx_remap[self.I_cells_Fi] node_match_threshold = tr.Property(depends_on='+GEO') def _get_node_match_threshold(self): min_length = np.min([self.a, self.b, self.c]) return min_length * 1e-4 unique_node_map = tr.Property(depends_on='+GEO') '''Property containing the mapping between the crease pattern nodes with duplicate nodes and pattern with compressed nodes array. The criterion for removing a node is geometric, the threshold is specified in node_match_threshold. ''' def _get_unique_node_map(self): # reshape the coordinates in array of segments to the shape (n_N, n_D x_0 = self.X_cells_Ia # construct distance vectors between every pair of nodes x_x_0 = x_0[:, np.newaxis, :] - x_0[np.newaxis, :, :] # calculate the distance between every pair of nodes dist_0 = np.sqrt(np.einsum('...i,...i', x_x_0, x_x_0)) # identify those at the same location zero_dist = dist_0 < self.node_match_threshold # get their indices i_idx, j_idx = np.where(zero_dist) # take only the upper triangle indices upper_triangle = i_idx < j_idx idx_multi, idx_delete = i_idx[upper_triangle], j_idx[upper_triangle] # construct a boolean array with True at valid and False at deleted # indices idx_unique = np.ones((len(x_0), ), dtype='bool') idx_unique[idx_delete] = False # Boolean array of nodes to keep - includes both those that # are unique and redirection nodes to be substituted for duplicates idx_keep = np.ones((len(x_0), ), dtype=np.bool_) idx_keep[idx_delete] = False # prepare the enumeration map map ij_map = np.ones_like(dist_0, dtype=np.int_) + len(x_0) i_ = np.arange(len(x_0)) # indexes of nodes that are being kept idx_row = i_[idx_keep] # enumerate the kept nodes by putting their number onto the diagonal ij_map[idx_keep, idx_keep] = np.arange(len(idx_row)) # broadcast the substitution nodes into the interaction positions ij_map[i_idx, j_idx] = ij_map[i_idx, i_idx] # get the substitution node by picking up the minimum index within ac column idx_remap = np.min(ij_map, axis=0) return idx_unique, idx_remap I_CDij = tr.Property(depends_on='+GEO') @tr.cached_property def _get_I_CDij(self): n_cells, n_ic, n_id, _, x_cell_idx, _, y_cell_idx = self.cell_map x_idx, y_idx = x_cell_idx / 2, y_cell_idx / 2 n_x_, n_y_ = len(x_idx), len(y_idx) I_cell_offset = (n_ic + np.arange(n_x_ * n_y_).reshape( n_x_, n_y_)) * self.wb_cell.n_I I_CDij_map = (I_cell_offset.T[:, :, np.newaxis, np.newaxis] + self.wb_cell.I_boundary[np.newaxis, np.newaxis, :, :]) return I_CDij_map def setup_plot(self, pb): self.pb = pb X_Ia = self.X_Ia.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) I_M = self.I_CDij[(0, -1), :, (0, -1), :] _, idx_remap = self.unique_node_map J_M = idx_remap[I_M] X_Ma = X_Ia[J_M.flatten()] k3d_mesh = k3d.mesh(X_Ia, I_Fi, color=0x999999, side='double') pb.objects['k3d_mesh'] = k3d_mesh pb.plot_fig += k3d_mesh if self.show_nodes: self._add_nodes_to_fig(pb, X_Ma) if self.wb_cell.show_node_labels: self._add_nodes_labels_to_fig(pb, X_Ia) if self.show_wireframe: self._add_wireframe_to_fig(pb, X_Ia, I_Fi) def update_plot(self, pb): X_Ia = self.X_Ia.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) I_M = self.I_CDij[(0, -1), :, (0, -1), :] _, idx_remap = self.unique_node_map J_M = idx_remap[I_M] X_Ma = X_Ia[J_M.flatten()] mesh = pb.objects['k3d_mesh'] mesh.vertices = X_Ia mesh.indices = I_Fi if self.show_nodes: if self.NODES in pb.objects: pb.objects[self.NODES].positions = X_Ma else: self._add_nodes_to_fig(pb, X_Ma) else: if self.NODES in pb.objects: pb.clear_object(self.NODES) if self.show_wireframe: if self.WIREFRAME in pb.objects: wireframe = pb.objects[self.WIREFRAME] wireframe.vertices = X_Ia wireframe.indices = I_Fi else: self._add_wireframe_to_fig(pb, X_Ia, I_Fi) else: if self.WIREFRAME in pb.objects: pb.clear_object(self.WIREFRAME) if self.show_node_labels: if self.NODES_LABELS in pb.objects: pb.clear_object(self.NODES_LABELS) self._add_nodes_labels_to_fig(pb, X_Ia) else: if self.NODES_LABELS in pb.objects: pb.clear_object(self.NODES_LABELS) def _add_nodes_labels_to_fig(self, pb, X_Ia): text_list = [] for I, X_a in enumerate(X_Ia): k3d_text = k3d.text('%g' % I, tuple(X_a), label_box=False, size=0.8, color=0x00FF00) pb.plot_fig += k3d_text text_list.append(k3d_text) pb.objects[self.NODES_LABELS] = text_list def _add_wireframe_to_fig(self, pb, X_Ia, I_Fi): k3d_mesh_wireframe = k3d.mesh(X_Ia, I_Fi, color=0x000000, wireframe=True) pb.plot_fig += k3d_mesh_wireframe pb.objects[self.WIREFRAME] = k3d_mesh_wireframe def _add_nodes_to_fig(self, pb, X_Ma): k3d_points = k3d.points(X_Ma, point_size=300) pb.objects[self.NODES] = k3d_points pb.plot_fig += k3d_points def _show_or_hide_fig_object(self, pb, show_obj, obj_name, obj_add_fun, obj_update_fun): if show_obj: if obj_name in pb.objects: obj_update_fun() else: obj_add_fun() else: if obj_name in pb.objects: pb.clear_object(obj_name) def export_fold_file(self, path=None): # See https://github.com/edemaine/fold/blob/master/doc/spec.md for fold file specification # Viewer: https://edemaine.github.io/fold/examples/foldviewer.html output_data = { "file_spec": 1, "file_creator": "BMCS software suite", "file_author": "RWTH Aachen - Institute of Structural Concrete", "file_title": "Preliminary Base", "file_classes": ["singleModel"], "frame_title": "Preliminary Base Crease Pattern", "frame_classes": ["creasePattern"], "vertices_coords": self.X_Ia.tolist(), "faces_vertices": self.I_Fi.tolist(), # To be completed } if path is None: path = time.strftime("%Y%m%d-%H%M%S") + '-shell.fold' with open(path, 'w') as outfile: json.dump(output_data, outfile, sort_keys=True, indent=4)
class ModelInteract(tr.HasTraits): models = tr.List([ ]) py_vars = tr.List(tr.Str) map_py2sp = tr.Dict d = tr.Float(0.03, GEO=True) h = tr.Float(0.8, GEO=True) # define the free parameters as traits with default, min and max values w_max = tr.Float(1.0) t = tr.Float(0.0001, min=1e-5, max=1) tau = tr.Float(0.5, interact=True) L_b = tr.Float(200, interact=True) E_f = tr.Float(100000, interact=True) A_f = tr.Float(20, interact=True) p = tr.Float(40, interact=True) E_m = tr.Float(26000, interact=True) A_m = tr.Float(100, interact=True) n_steps = tr.Int(50) sliders = tr.Property @tr.cached_property def _get_sliders(self): traits = self.traits(interact=True) vals = self.trait_get(interact=True) slider_names = self.py_vars[1:] max_vals = {name: getattr(traits, 'max', vals[name] * 2) for name in slider_names} t_slider = {'t': ipw.FloatSlider(1e-5, min=1e-5, max=1, step=0.05, description=r'\(t\)')} param_sliders = {name: ipw.FloatSlider(value=vals[name], min=1e-5, max=max_vals[name], step=max_vals[name] / self.n_steps, description=r'\(%s\)' % self.map_py2sp[name].name) for (name, _) in traits.items() } t_slider.update(param_sliders) return t_slider w_range = tr.Property(tr.Array(np.float_), depends_on='w_max') @tr.cached_property def _get_w_range(self): return np.linspace(0, self.w_max, 50) x_range = tr.Property(tr.Array(np.float_), depends_on='L_b') @tr.cached_property def _get_x_range(self): return np.linspace(-self.L_b, 0, 100) model_plots = tr.Property(tr.List) @tr.cached_property def _get_model_plots(self): return [PlotModel(itr=self, model=m) for m in self.models] def init_fields(self): self.fig, ((self.ax_po, self.ax_u), (self.ax_eps, self.ax_tau)) = plt.subplots( 2, 2, figsize=(9, 5), tight_layout=True ) values = self.trait_get(interact=True) params = list(values[py_var] for py_var in self.py_vars[1:]) for mp in self.model_plots: mp.init_fields(*params) mp.init_Pw(*params) self.ax_po.set_xlim(0, self.w_max * 1.05) def clear_fields(self): clear_plot(self.ax_po, self.ax_u, self.ax_eps, self.ax_tau) def update_fields(self, t, **values): w = t * self.w_max self.trait_set(**values) params = list(values[py_var] for py_var in self.py_vars[1:]) L_b = self.L_b self.clear_fields() for mp in self.model_plots: mp.update_fields(w, *params) mp.update_Pw(w, *params) P_max = np.max(np.array([m.P_max for m in self.model_plots])) self.ax_po.set_ylim(0, P_max * 1.05) self.ax_po.set_xlim(0, self.w_max * 1.05) u_min = np.min(np.array([m.u_min for m in self.model_plots])) u_max = np.max(np.array([m.u_max for m in self.model_plots] + [1])) self.ax_u.set_ylim(u_min, u_max * 1.1) self.ax_u.set_xlim(xmin=-1.05 * L_b, xmax=0.05 * L_b) eps_min = np.min(np.array([m.eps_min for m in self.model_plots])) eps_max = np.max(np.array([m.eps_max for m in self.model_plots])) self.ax_eps.set_ylim(eps_min, eps_max * 1.1) self.ax_eps.set_xlim(xmin=-1.05 * L_b, xmax=0.05 * L_b) self.ax_tau.set_ylim(0, self.tau * 1.1) self.ax_tau.set_xlim(xmin=-1.05 * L_b, xmax=0.05 * L_b) self.fig.canvas.draw_idle() def set_w_max_fields(self, w_max): self.w_max = w_max values = {name: slider.value for name, slider in self.sliders.items()} self.update_fields(**values) def interact_fields(self): self.init_fields() self.on_w_max_change = self.update_fields sliders = self.sliders out = ipw.interactive_output(self.update_fields, sliders) self.widget_layout(out) #========================================================================= # Interaction on the pull-out curve spatial plot #========================================================================= def init_geometry(self): self.fig, (self.ax_po, self.ax_geo) = plt.subplots( 1, 2, figsize=(8, 3.4)) # , tight_layout=True) values = self.trait_get(interact=True) params = list(values[py_var] for py_var in self.py_vars[1:]) h = self.h x_C = np.array([[-1, 0], [0, 0], [0, h], [-1, h]], dtype=np.float_) self.line_C, = self.ax_geo.fill(*x_C.T, color='gray', alpha=0.3) for mp in self.model_plots: mp.line_aw, = self.ax_geo.fill([], [], color='white', alpha=1) mp.line_F, = self.ax_geo.fill([], [], color='black', alpha=0.8) mp.line_F0, = self.ax_geo.fill([], [], color='white', alpha=1) mp.init_Pw(*params) self.ax_po.set_xlim(0, self.w_max * 1.05) def clear_geometry(self): clear_plot(self.ax_po, self.ax_geo) def update_geometry(self, t, **values): w = t * self.w_max self.clear_geometry() self.trait_set(**values) params = list(values[py_var] for py_var in self.py_vars[1:]) h = self.h d = self.d L_b = self.L_b f_top = h / 2 + d / 2 f_bot = h / 2 - d / 2 self.ax_geo.set_xlim( xmin=-1.05 * L_b, xmax=max(0.05 * L_b, 1.1 * self.w_max)) x_C = np.array([[-L_b, 0], [0, 0], [0, h], [-L_b, h]], dtype=np.float_) self.line_C.set_xy(x_C) for mp in self.model_plots: a_val = mp.model.get_aw_pull(w, *params) width = d * 0.5 x_a = np.array([[a_val, f_bot - width], [0, f_bot - width], [0, f_top + width], [a_val, f_top + width]], dtype=np.float_) mp.line_aw.set_xy(x_a) w_L_b = mp.model.get_w_L_b(w, *params) x_F = np.array([[-L_b + w_L_b, f_bot], [w, f_bot], [w, f_top], [-L_b + w_L_b, f_top]], dtype=np.float_) mp.line_F.set_xy(x_F) x_F0 = np.array([[-L_b, f_bot], [-L_b + w_L_b, f_bot], [-L_b + w_L_b, f_top], [-L_b, f_top]], dtype=np.float_) mp.line_F0.set_xy(x_F0) mp.update_Pw(w, *params) P_max = np.max(np.array([mp.P_max for mp in self.model_plots])) self.ax_po.set_ylim(0, P_max * 1.1) self.ax_po.set_xlim(0, self.w_max * 1.05) self.fig.canvas.draw_idle() def set_w_max(self, w_max): self.w_max = w_max values = {name: slider.value for name, slider in self.sliders.items()} self.on_w_max_change(**values) on_w_max_change = tr.Callable def interact_geometry(self): self.init_geometry() self.on_w_max_change = self.update_geometry sliders = self.sliders out = ipw.interactive_output(self.update_geometry, sliders) self.widget_layout(out) def widget_layout(self, out): sliders = self.sliders layout = ipw.Layout(grid_template_columns='1fr 1fr') param_sliders_list = [sliders[py_var] for py_var in self.py_vars[1:]] t_slider = sliders['t'] grid = ipw.GridBox(param_sliders_list, layout=layout) w_max_text = ipw.FloatText( value=self.w_max, description=r'w_max', disabled=False ) out_w_max = ipw.interactive_output(self.set_w_max, {'w_max': w_max_text}) hbox = ipw.HBox([t_slider, w_max_text]) box = ipw.VBox([hbox, grid, out, out_w_max]) display(box)
class MATS3DMplCSDODF(MATS3DEval): '''Tangential constitutive law parameters ''' gamma_T = Float(5000., label="Gamma", desc=" Tangential Kinematic hardening modulus", enter_set=True, auto_set=False) K_T = Float(10.0, label="K", desc="Tangential Isotropic harening", enter_set=True, auto_set=False) S = Float(0.00001, label="S", desc="Damage strength", enter_set=True, auto_set=False) r = Float(1.2, label="r", desc="Damage cumulation parameter", enter_set=True, auto_set=False) c = Float(1.0, label="c", desc="Damage cumulation parameter", enter_set=True, auto_set=False) tau_pi_bar = Float(2.0, label="Tau_bar", desc="Reversibility limit", enter_set=True, auto_set=False) a = Float(0.0, label="a", desc="Lateral pressure coefficient", enter_set=True, auto_set=False) #------------------------------------------- # Normal_Tension constitutive law parameters #------------------------------------------- Ad = Float(10000.0, label="a", desc="brittleness coefficient", enter_set=True, auto_set=False) eps_0 = Float(56e-6, label="a", desc="threshold strain", enter_set=True, auto_set=False) eps_f = Float(250e-6, label="a", desc="threshold strain", enter_set=True, auto_set=False) #----------------------------------------------- # Normal_Compression constitutive law parameters #----------------------------------------------- K_N = Float(10000., label="K_N", desc=" Normal isotropic harening", enter_set=True, auto_set=False) gamma_N = Float(15000., label="gamma_N", desc="Normal kinematic hardening", enter_set=True, auto_set=False) sigma_0 = Float(20., label="sigma_0", desc="Yielding stress", enter_set=True, auto_set=False) zeta_G = Float(1.0, label="zeta_G", desc="anisotropy parameter", enter_set=True, auto_set=False) state_var_shapes = tr.Property(tr.Dict(), depends_on='n_mp') '''Dictionary of state variable entries with their array shapes. ''' @cached_property def _get_state_shapes(self): return { 'w_N_Emn': (self.n_mp, ), 'z_N_Emn': (self.n_mp, ), 'alpha_N_Emn': (self.n_mp, ), 'r_N_Emn': (self.n_mp, ), 'eps_N_p_Emn': (self.n_mp, ), 'sigma_N_Emn': (self.n_mp, ), 'w_T_Emn': (self.n_mp, ), 'z_T_Emn': (self.n_mp, ), 'alpha_T_Emna': (self.n_mp, 3), 'eps_T_pi_Emna': (self.n_mp, 3), } #-------------------------------------------------------------- # microplane constitutive law (normal behavior CP + TD) #-------------------------------------------------------------- def get_normal_law(self, eps_N_Emn, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn): E_N = self.E / (1.0 - 2.0 * self.nu) pos = eps_N_Emn > 1e-6 H = 1.0 * pos sigma_n_trial = (1.0 - H * w_N_Emn) * E_N * (eps_N_Emn - eps_N_p_Emn) Z = self.K_N * r_N_Emn X = self.gamma_N * alpha_N_Emn h = self.sigma_0 + Z pos_iso = h > 1e-6 f_trial = abs(sigma_n_trial - X) - h * pos_iso thres_1 = f_trial > 1e-6 delta_lamda = f_trial / \ (E_N + abs(self.K_N) + self.gamma_N) * thres_1 eps_N_p_Emn = eps_N_p_Emn + delta_lamda * sign(sigma_n_trial - X) r_N_Emn = r_N_Emn + delta_lamda alpha_N_Emn = alpha_N_Emn + delta_lamda * sign(sigma_n_trial - X) # def Z_N(z_N_Emn): return 1.0 / self.Ad * (-z_N_Emn) / (1.0 + z_N_Emn) # Y_N = 0.5 * H * E_N * eps_N_Emn ** 2.0 # Y_0 = 0.5 * E_N * self.eps_0 ** 2.0 # f = Y_N - (Y_0 + Z_N(z_N_Emn)) # # thres_2 = f > 1e-6 # # def f_w(Y): return 1.0 - 1.0 / (1.0 + self.Ad * (Y - Y_0)) # w_N_Emn = f_w(Y_N) * thres_2 # z_N_Emn = - w_N_Emn * thres_2 f_trial_Emn = eps_N_Emn - self.eps_0 f_idx = np.where(f_trial_Emn > 0) z_N_Emn[f_idx] = eps_N_Emn[f_idx] w_N_Emn[f_idx] = (1. - (self.eps_0 / z_N_Emn[f_idx]) * np.exp(-(z_N_Emn[f_idx] - self.eps_0) / (self.eps_f - self.eps_0))) sigma_N_Emn = (1.0 - H * w_N_Emn) * E_N * (eps_N_Emn - eps_N_p_Emn) return w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn #------------------------------------------------------------------------- # microplane constitutive law (Tangential CSD)-(Pressure sensitive cumulative damage) #------------------------------------------------------------------------- def get_tangential_law(self, eps_T_Emna, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_kk): E_T = self.E / (1.0 + self.nu) sig_pi_trial = E_T * (eps_T_Emna - eps_T_pi_Emna) Z = self.K_T * z_T_Emn X = self.gamma_T * alpha_T_Emna norm_1 = sqrt( einsum('Emna,Emna -> Emn', (sig_pi_trial - X), (sig_pi_trial - X))) f = norm_1 - self.tau_pi_bar - \ Z + self.a * sigma_kk plas_1 = f > 1e-6 elas_1 = f < 1e-6 delta_lamda = f / \ (E_T / (1.0 - w_T_Emn) + self.gamma_T + self.K_T) * plas_1 # print 'f', f # print 'w_T_Emn', w_T_Emn # print 'delta_lamda', delta_lamda # print '*****' norm_2 = 1.0 * elas_1 + sqrt( einsum('Emna,Emna -> Emn', (sig_pi_trial - X), (sig_pi_trial - X))) * plas_1 eps_T_pi_Emna[..., 0] = eps_T_pi_Emna[..., 0] + plas_1 * delta_lamda * \ ((sig_pi_trial[..., 0] - X[..., 0]) / (1.0 - w_T_Emn)) / norm_2 eps_T_pi_Emna[..., 1] = eps_T_pi_Emna[..., 1] + plas_1 * delta_lamda * \ ((sig_pi_trial[..., 1] - X[..., 1]) / (1.0 - w_T_Emn)) / norm_2 eps_T_pi_Emna[..., 2] = eps_T_pi_Emna[..., 2] + plas_1 * delta_lamda * \ ((sig_pi_trial[..., 2] - X[..., 2]) / (1.0 - w_T_Emn)) / norm_2 Y = 0.5 * E_T * \ einsum('Emna,Emna -> Emn', (eps_T_Emna - eps_T_pi_Emna), (eps_T_Emna - eps_T_pi_Emna)) w_T_Emn += ((1.0 - w_T_Emn) ** self.c) * \ (delta_lamda * (Y / self.S) ** self.r) # * \ #(self.tau_pi_bar / (self.tau_pi_bar - self.a * sigma_kk / 3.0)) alpha_T_Emna[..., 0] = alpha_T_Emna[..., 0] + plas_1 * delta_lamda *\ (sig_pi_trial[..., 0] - X[..., 0]) / norm_2 alpha_T_Emna[..., 1] = alpha_T_Emna[..., 1] + plas_1 * delta_lamda *\ (sig_pi_trial[..., 1] - X[..., 1]) / norm_2 alpha_T_Emna[..., 2] = alpha_T_Emna[..., 2] + plas_1 * delta_lamda *\ (sig_pi_trial[..., 2] - X[..., 2]) / norm_2 z_T_Emn = z_T_Emn + delta_lamda return w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna #------------------------------------------------------------------------- # MICROPLANE-Kinematic constraints #------------------------------------------------------------------------- def _get_e_Emna(self, eps_Emab): # Projection of apparent strain onto the individual microplanes e_ni = einsum('nb,Emba->Emna', self._MPN, eps_Emab) return e_ni def _get_e_N_Emn(self, e_Emna): # get the normal strain array for each microplane e_N_Emn = einsum('Emna, na->Emn', e_Emna, self._MPN) return e_N_Emn def _get_e_T_Emna(self, e_Emna): # get the tangential strain vector array for each microplane e_N_Emn = self._get_e_N_Emn(e_Emna) e_N_Emna = einsum('Emn,na->Emna', e_N_Emn, self._MPN) return e_Emna - e_N_Emna #------------------------------------------------- # Alternative methods for the kinematic constraint #------------------------------------------------- # get the dyadic product of the microplane normals _MPNN = Property(depends_on='n_mp') @cached_property def _get__MPNN(self): MPNN_nij = einsum('ni,nj->nij', self._MPN, self._MPN) return MPNN_nij # get the third order tangential tensor (operator) for each microplane _MPTT = Property(depends_on='n_mp') @cached_property def _get__MPTT(self): delta = identity(3) MPTT_nijr = 0.5 * ( einsum('ni,jr -> nijr', self._MPN, delta) + einsum('nj,ir -> njir', self._MPN, delta) - 2 * einsum('ni,nj,nr -> nijr', self._MPN, self._MPN, self._MPN)) return MPTT_nijr def _get_e_N_Emn_2(self, eps_Emab): # Projection of apparent strain onto the individual microplanes return einsum('nij,Emij->Emn', self._MPNN, eps_Emab) def _get_e_T_Emna_2(self, eps_Emab): # get the normal strain array for each microplane MPTT_ijr = self._get__MPTT() return einsum('nija,Emij->Emna', MPTT_ijr, eps_Emab) def _get_e_Emna_2(self, eps_Emab): # get the tangential strain vector array for each microplane return self._get_e_N_Emn_2(eps_Emab) * self._MPN +\ self._get_e_T_Emna_2(eps_Emab) #-------------------------------------------------------- # return the state variables (Damage , inelastic strains) #-------------------------------------------------------- def _get_state_variables(self, sctx, eps_app_eng, sigma_kk): e_N_arr = self._get_e_N_arr_2(eps_app_eng) e_T_vct_arr = self._get_e_T_vct_arr_2(eps_app_eng) sctx_arr = zeros_like(sctx) sctx_N = self.get_normal_law(e_N_arr, sctx) sctx_arr[:, 0:5] = sctx_N sctx_tangential = self.get_tangential_law(e_T_vct_arr, sctx, sigma_kk) sctx_arr[:, 5:13] = sctx_tangential return sctx_arr #----------------------------------------------------------------- # Returns a list of the plastic normal strain for all microplanes. #----------------------------------------------------------------- def _get_eps_N_p_Emn(self, eps_Emab, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn): eps_N_Emn = self._get_e_N_Emn_2(eps_Emab) eps_N_p_Emn = self.get_normal_law(eps_N_Emn, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn)[4] return eps_N_p_Emn #---------------------------------------------------------------- # Returns a list of the sliding strain vector for all microplanes. #---------------------------------------------------------------- def _get_eps_T_pi_Emna(self, eps_Emab, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_kk_Em): eps_T_Emna = self._get_e_T_Emna_2(eps_Emab) eps_N_T_pi_Emna = self.get_tangential_law(eps_T_Emna, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_kk_Em)[3] return eps_N_T_pi_Emna #------------------------------------------------------------- # Returns a list of the integrity factors for all microplanes. #------------------------------------------------------------- def _get_phi_Emn(self, eps_Emab, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn): eps_N_Emn = self._get_e_N_Emn_2(eps_Emab) eps_T_Emna = self._get_e_T_Emna_2(eps_Emab) w_N_Emn = self.get_normal_law(eps_N_Emn, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn)[0] sigma_N_Emn = self.get_normal_law(eps_N_Emn, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn)[5] w_T_Emn = self.get_tangential_law(eps_T_Emna, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn)[0] w_Emn = zeros_like(w_N_Emn) # w_Emn = np.zeros_like(w_N_Emn) # idx_1 = np.where(sigma_N_Emn >= 0) # w_Emn[idx_1] = w_N_Emn[idx_1] # # idx_2 = np.where(sigma_N_Emn < 0) # w_Emn[idx_2] = w_T_Emn[idx_2] # eig = np.linalg.eig(eps_Emab)[0] ter_1 = np.sum(eig) idx_1 = np.where(ter_1 > 0.0) idx_2 = np.where(ter_1 <= 0.0) w_Emn[idx_1] = w_N_Emn[idx_1] w_Emn[idx_2] = w_T_Emn[idx_2] phi_Emn = 1.0 - w_Emn return phi_Emn #---------------------------------------------- # Returns the 2nd order damage tensor 'phi_mtx' #---------------------------------------------- def _get_phi_Emab(self, phi_Emn): # integration terms for each microplanes phi_Emab = einsum('Emn,n,nab->Emab', phi_Emn, self._MPW, self._MPNN) return phi_Emab # # #---------------------------------------------------------------------- # # Returns the 4th order damage tensor 'beta4' using sum-type symmetrization # #(cf. [Jir99], Eq.(21)) # #---------------------------------------------------------------------- # def _get_beta_Emabcd(self, phi_Emab): # # delta = identity(3) # # # use numpy functionality (einsum) to evaluate [Jir99], Eq.(21) # beta_Emabcd = 0.25 * (einsum('Emik,jl->Emijkl', phi_Emab, delta) + # einsum('Emil,jk->Emijkl', phi_Emab, delta) + # einsum('Emjk,il->Emijkl', phi_Emab, delta) + # einsum('Emjl,ik->Emijkl', phi_Emab, delta)) # # return beta_Emabcd # # # #---------------------------------------------------------------------- # # # Returns the 4th order damage tensor 'beta4' using product-type symmetrization # # #(cf. [Baz97], Eq.(87)) # # #---------------------------------------------------------------------- # # def _get_beta_tns_product_type(self, sctx, eps_app_eng, sigma_kk): # # # # delta = identity(2) # # # # phi_mtx = self._get_phi_mtx(sctx, eps_app_eng, sigma_kk) # # # # n_dim = 2 # # phi_eig_value, phi_eig_mtx = eigh(phi_mtx) # # phi_eig_value_real = array([pe.real for pe in phi_eig_value]) # # phi_pdc_mtx = zeros((n_dim, n_dim), dtype=float) # # for i in range(n_dim): # # phi_pdc_mtx[i, i] = phi_eig_value_real[i] # # # w_mtx = tensorial square root of the second order damage tensor: # # w_pdc_mtx = sqrt(phi_pdc_mtx) # # # # # transform the matrix w back to x-y-coordinates: # # w_mtx = einsum('ik,kl,lj -> ij', phi_eig_mtx, w_pdc_mtx, phi_eig_mtx) # # #w_mtx = dot(dot(phi_eig_mtx, w_pdc_mtx), transpose(phi_eig_mtx)) # # # # beta_ijkl = 0.5 * \ # # (einsum('ik,jl -> ijkl', w_mtx, w_mtx) + # # einsum('il,jk -> ijkl', w_mtx, w_mtx)) # # # # return beta_ijkl # # #----------------------------------------------------------- # # Integration of the (inelastic) strains for each microplane # #----------------------------------------------------------- # def _get_eps_p_Emab(self, eps_Emab, w_N_Emn, z_N_Emn, # alpha_N_Emn, r_N_Emn, eps_N_p_Emn, # w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn): # # eps_N_Emn = self._get_e_N_Emn_2(eps_Emab) # eps_T_Emna = self._get_e_T_Emna_2(eps_Emab) # # # # plastic normal strains # # eps_N_p_Emn = self.get_normal_law( # # eps_N_Emn, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn)[5] # # eps_N_p_Emn = self._get_eps_N_p_Emn( # eps_Emab, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn) # # # # sliding tangential strains # # eps_T_pi_Emna = self.get_tangential_law( # # eps_T_Emna, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, # # sigma_N_Emn)[4] # # eps_T_pi_Emna = self._get_eps_T_pi_Emna( # eps_Emab, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) # # delta = identity(3) # # # 2-nd order plastic (inelastic) tensor # eps_p_Emab = einsum('n,Emn,na,nb -> Emab', self._MPW, eps_N_p_Emn, self._MPN, self._MPN) + \ # 0.5 * (einsum('n,Emnf,na,fb-> Emab', self._MPW, eps_T_pi_Emna, self._MPN, delta) + # einsum('n,Emnf,nb,fa-> Emab', self._MPW, eps_T_pi_Emna, self._MPN, delta)) # # return eps_p_Emab # # #------------------------------------------------------------------------- # # Evaluation - get the corrector and predictor # #------------------------------------------------------------------------- # # def get_corr_pred(self, eps_Emab_n1, deps_Emab, tn, tn1, update_state, w_N_Emn, z_N_Emn, # alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, # w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna): # # # Corrector predictor computation. # if update_state: # # eps_Emab_n = eps_Emab_n1 - deps_Emab # # eps_N_Emn = self._get_e_N_Emn_2(eps_Emab_n) # eps_T_Emna = self._get_e_T_Emna_2(eps_Emab_n) # # w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn = self.get_normal_law( # eps_N_Emn, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn) # w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna = self.get_tangential_law( # eps_T_Emna, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) # # #------------------------------------------------------------------ # # Damage tensor (4th order) using product- or sum-type symmetrization: # #------------------------------------------------------------------ # phi_Emn = self._get_phi_Emn(eps_Emab_n1, w_N_Emn, z_N_Emn, # alpha_N_Emn, r_N_Emn, eps_N_p_Emn, # w_T_Emn, z_T_Emn, alpha_T_Emna, # eps_T_pi_Emna, sigma_N_Emn) # # phi_Emab = self._get_phi_Emab(phi_Emn) # # beta_Emabcd = self._get_beta_Emabcd(phi_Emab) # # #------------------------------------------------------------------ # # Damaged stiffness tensor calculated based on the damage tensor beta4: # #------------------------------------------------------------------ # D_Emabcd = einsum( # 'Emijab, abef, Emcdef -> Emijcd', beta_Emabcd, self.D_abef, beta_Emabcd) # #---------------------------------------------------------------------- # # Return stresses (corrector) and damaged secant stiffness matrix (predictor) # #---------------------------------------------------------------------- # # plastic strain tensor # eps_p_Emab = self._get_eps_p_Emab(eps_Emab_n1, w_N_Emn, z_N_Emn, # alpha_N_Emn, r_N_Emn, eps_N_p_Emn, # w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) # # # elastic strain tensor # eps_e_Emab = eps_Emab_n1 - eps_p_Emab # # # calculation of the stress tensor # sig_Emab = einsum('Emabcd,Emcd -> Emab', D_Emabcd, eps_e_Emab) # # return D_Emabcd, sig_Emab #---------------------------------------------------------------- # the fourth order volumetric-identity tensor #---------------------------------------------------------------- def _get_I_vol_abcd(self): delta = identity(3) I_vol_abcd = (1.0 / 3.0) * einsum('ab,cd -> abcd', delta, delta) return I_vol_abcd #---------------------------------------------------------------- # Returns the fourth order deviatoric-identity tensor #---------------------------------------------------------------- def _get_I_dev_abcd(self): delta = identity(3) I_dev_abcd = 0.5 * (einsum('ac,bd -> abcd', delta, delta) + einsum('ad,bc -> abcd', delta, delta)) \ - (1.0 / 3.0) * einsum('ab,cd -> abcd', delta, delta) return I_dev_abcd #---------------------------------------------------------------- # Returns the fourth order tensor P_vol [Wu.2009] #---------------------------------------------------------------- def _get_P_vol_ab(self): delta = identity(3) P_vol_ab = (1.0 / 3.0) * delta return P_vol_ab #---------------------------------------------------------------- # Returns the fourth order tensor P_dev [Wu.2009] #---------------------------------------------------------------- def _get_P_dev_nabc(self): delta = identity(3) P_dev_nabc = 0.5 * einsum('nd,da,bc -> nabc', self._MPN, delta, delta) return P_dev_nabc #---------------------------------------------------------------- # Returns the outer product of P_vol [Wu.2009] #---------------------------------------------------------------- def _get_PP_vol_abcd(self): delta = identity(3) PP_vol_abcd = (1.0 / 9.0) * einsum('ab,cd -> abcd', delta, delta) return PP_vol_abcd #---------------------------------------------------------------- # Returns the inner product of P_dev #---------------------------------------------------------------- def _get_PP_dev_nabcd(self): delta = identity(3) PP_dev_nabcd = 0.5 * (0.5 * (einsum('na,nc,bd -> nabcd', self._MPN, self._MPN, delta) + einsum('na,nd,bc -> nabcd', self._MPN, self._MPN, delta)) + 0.5 * (einsum('ac,nb,nd -> nabcd', delta, self._MPN, self._MPN) + einsum('ad,nb,nc -> nabcd', delta, self._MPN, self._MPN))) -\ (1.0 / 3.0) * (einsum('na,nb,cd -> nabcd', self._MPN, self._MPN, delta) + einsum('ab,nc,nd -> nabcd', delta, self._MPN, self._MPN)) +\ (1.0 / 9.0) * einsum('ab,cd -> abcd', delta, delta) return PP_dev_nabcd def _get_I_vol_4(self): # The fourth order volumetric-identity tensor delta = identity(3) I_vol_ijkl = (1.0 / 3.0) * einsum('ij,kl -> ijkl', delta, delta) return I_vol_ijkl def _get_I_dev_4(self): # The fourth order deviatoric-identity tensor delta = identity(3) I_dev_ijkl = 0.5 * (einsum('ik,jl -> ijkl', delta, delta) + einsum('il,jk -> ijkl', delta, delta)) \ - (1 / 3.0) * einsum('ij,kl -> ijkl', delta, delta) return I_dev_ijkl def _get_P_vol(self): delta = identity(3) P_vol_ij = (1 / 3.0) * delta return P_vol_ij def _get_P_dev(self): delta = identity(3) P_dev_njkl = 0.5 * einsum('ni,ij,kl -> njkl', self._MPN, delta, delta) return P_dev_njkl def _get_PP_vol_4(self): # outer product of P_vol delta = identity(3) PP_vol_ijkl = (1 / 9.) * einsum('ij,kl -> ijkl', delta, delta) return PP_vol_ijkl def _get_PP_dev_4(self): # inner product of P_dev delta = identity(3) PP_dev_nijkl = 0.5 * (0.5 * (einsum('ni,nk,jl -> nijkl', self._MPN, self._MPN, delta) + einsum('ni,nl,jk -> nijkl', self._MPN, self._MPN, delta)) + 0.5 * (einsum('ik,nj,nl -> nijkl', delta, self._MPN, self._MPN) + einsum('il,nj,nk -> nijkl', delta, self._MPN, self._MPN))) -\ (1 / 3.) * (einsum('ni,nj,kl -> nijkl', self._MPN, self._MPN, delta) + einsum('ij,nk,nl -> nijkl', delta, self._MPN, self._MPN)) +\ (1 / 9.) * einsum('ij,kl -> ijkl', delta, delta) return PP_dev_nijkl #-------------------------------------------------------------------------- # Returns the fourth order secant stiffness tensor (cf. [Wu.2009], Eq.(29)) #-------------------------------------------------------------------------- def _get_S_1_Emabcd(self, eps_Emab, phi_Emn): K0 = self.E / (1.0 - 2.0 * self.nu) G0 = self.E / (1.0 + self.nu) #phi_Emn = 1.0 - self._get_omega(kappa) # print 'phi_Emn', phi_Emn PP_vol_abcd = self._get_PP_vol_abcd() PP_dev_nabcd = self._get_PP_dev_nabcd() I_dev_abcd = self._get_I_dev_abcd() # PP_vol_abcd = self._get_PP_vol_4() # PP_dev_nabcd = self._get_PP_dev_4() # I_dev_abcd = self._get_I_dev_4() S_1_Emabcd = K0 * einsum('Emn, n, abcd-> Emabcd', phi_Emn, self._MPW, PP_vol_abcd) + \ G0 * 2.0 * self.zeta_G * einsum('Emn, n, nabcd-> Emabcd', phi_Emn, self._MPW, PP_dev_nabcd) - (1.0 / 3.0) * ( 2.0 * self.zeta_G - 1.0) * G0 * einsum('Emn, n, abcd-> Emabcd', phi_Emn, self._MPW, I_dev_abcd) return S_1_Emabcd # #------------------------------------------ # # scalar damage factor for each microplane # #------------------------------------------ # def _get_d_Em(self, s_Emng, eps_Emab): # # d_Emn = 1.0 - self.get_state_variables(s_Emng, eps_Emab)[0] # # d_Em = (1.0 / 3.0) * einsum('Emn,n-> Em', d_Emn, self._MPW) # # return d_Em # # #------------------------------------------ # # The 4th order volumetric damage tensor # #------------------------------------------ # def _get_M_vol_abcd(self, sctx, eps_app_eng, sigma_kk): # # d = self._get_Em( s_Emng, eps_Emab) # delta = identity(2) # # I_4th_abcd = 0.5 * (einsum('ac,bd -> ijkl', delta, delta) + # einsum('il,jk -> ijkl', delta, delta)) # # # print 'M_vol', (1 - d) * I_4th_ijkl # # return (1 - d) * I_4th_ijkl # # #------------------------------------------ # # The 4th order deviatoric damage tensor # #------------------------------------------ # def _get_M_dev_tns(self, phi_mtx): # # delta = identity(3) # I_4th_ijkl = 0.5 * (einsum('ik,jl -> ijkl', delta, delta) + # einsum('il,jk -> ijkl', delta, delta)) # tr_phi_mtx = np.trace(phi_mtx) # # M_dev_ijkl = self.zeta_G * (0.5 * (einsum('ik,jl->ijkl', delta, phi_mtx) + # einsum('il,jk->ijkl', delta, phi_mtx)) + # 0.5 * (einsum('ik,jl->ijkl', phi_mtx, delta) + # einsum('il,jk->ijkl', phi_mtx, delta))) \ # - (2. * self.zeta_G - 1.) * (tr_phi_mtx / 3.) * I_4th_ijkl # # return M_dev_ijkl # # #-------------------------------------------------------------------------- # # Returns the fourth order secant stiffness tensor (cf. [Wu.2009], Eq.(31)) # #-------------------------------------------------------------------------- # def _get_S_2_Emabcd(self, sctx, eps_app_eng, sigma_kk): # # K0 = self.E / (1. - 2. * self.nu) # G0 = self.E / (1. + self.nu) # # I_vol_ijkl = self._get_I_vol_4() # I_dev_ijkl = self._get_I_dev_4() # phi_mtx = self._get_phi_mtx(sctx, eps_app_eng, sigma_kk) # M_vol_ijkl = self._get_M_vol_tns(sctx, eps_app_eng, sigma_kk) # M_dev_ijkl = self._get_M_dev_tns(phi_mtx) # # S_2_ijkl = K0 * einsum('ijmn,mnrs,rskl -> ijkl', I_vol_ijkl, M_vol_ijkl, I_vol_ijkl ) \ # + G0 * einsum('ijmn,mnrs,rskl -> ijkl', I_dev_ijkl, M_dev_ijkl, I_dev_ijkl)\ # # return S_2_ijkl # # #-------------------------------------------------------------------------- # # Returns the fourth order secant stiffness tensor (cf. [Wu.2009], Eq.(34)) # #-------------------------------------------------------------------------- # def _get_S_3_Emabcd(self, sctx, eps_app_eng, sigma_kk): # # K0 = self.E / (1. - 2. * self.nu) # G0 = self.E / (1. + self.nu) # # I_vol_ijkl = self._get_I_vol_4() # I_dev_ijkl = self._get_I_dev_4() # # # The fourth order elastic stiffness tensor # S_0_ijkl = K0 * I_vol_ijkl + G0 * I_dev_ijkl # # d_n = self._get_state_variables(sctx, eps_app_eng, sigma_kk)[:, 5] # # PP_vol_4 = self._get_PP_vol_4() # PP_dev_4 = self._get_PP_dev_4() # # delta = identity(3) # I_4th_ijkl = einsum('ik,jl -> ijkl', delta, delta) # # D_ijkl = einsum('n,n,ijkl->ijkl', d_n, self._MPW, PP_vol_4) + \ # 2 * self.zeta_G * einsum('n,n,nijkl->ijkl', d_n, self._MPW, PP_dev_4) - ( # 1 / 3.) * (2 * self.zeta_G - 1) * einsum('n,n,ijkl->ijkl', d_n, self._MPW, I_dev_ijkl) # # phi_ijkl = (I_4th_ijkl - D_ijkl) # # S_ijkl = einsum('ijmn,mnkl', phi_ijkl, S_0_ijkl) # # return S_ijkl # #------------------------------------------------------------------------- # Returns the fourth order secant stiffness tensor with the (double orthotropic) assumption #------------------------------------------------------------------------- def _get_S_4_Emabcd(self, eps_Emab, phi_Emab): K0 = self.E / (1.0 - 2.0 * self.nu) G0 = self.E / (1.0 + self.nu) I_vol_abcd = self._get_I_vol_abcd() I_dev_abcd = self._get_I_dev_abcd() delta = identity(3) #phi_Emab = self._get_phi_Emab(kappa) D_Emab = delta - phi_Emab d_Em = (1.0 / 3.0) * np.einsum('Emaa -> Em', D_Emab) D_bar_Emab = self.zeta_G * \ (D_Emab - np.einsum('Em, ab -> Emab', d_Em, delta)) S_4_Emabcd = K0 * I_vol_abcd - K0 * einsum('Em,abcd -> Emabcd', d_Em, I_vol_abcd) +\ G0 * I_dev_abcd - G0 * einsum('Em,abcd -> Emabcd', d_Em, I_dev_abcd) +\ (2.0 / 3.0) * (G0 - K0) * (einsum('ij,Emkl -> Emijkl', delta, D_bar_Emab) + einsum('Emij,kl -> Emijkl', D_bar_Emab, delta)) + 0.5 * (- K0 + 2.0 * G0) *\ (0.5 * (einsum('ik,Emjl -> Emijkl', delta, D_bar_Emab) + einsum('Emil,jk -> Emijkl', D_bar_Emab, delta)) + 0.5 * (einsum('Emil,jk -> Emijkl', D_bar_Emab, delta) + einsum('ik,Emjl -> Emijkl', delta, D_bar_Emab))) return S_4_Emabcd #---------------------------------------------------------------------- # Returns the fourth order secant stiffness tensor (restrctive orthotropic) #---------------------------------------------------------------------- def _get_S_5_Emabcd(self, eps_Emab, phi_Emab): K0 = self.E / (1.0 - 2.0 * self.nu) G0 = self.E / (1.0 + self.nu) delta = identity(3) # commented out - @rch, @abdul please check # phi_Emab = self._get_phi_Emab(kappa) # damaged stiffness without simplification S_5_Emabcd = (1.0 / 3.0) * (K0 - G0) * 0.5 * ((einsum('ij,Emkl -> Emijkl', delta, phi_Emab) + einsum('Emij,kl -> Emijkl', phi_Emab, delta))) + \ G0 * 0.5 * ((0.5 * (einsum('ik,Emjl -> Emijkl', delta, phi_Emab) + einsum('Emil,jk -> Emijkl', phi_Emab, delta)) + 0.5 * (einsum('Emik,jl -> ijkl', phi_Emab, delta) + einsum('il,Emjk -> Emijkl', delta, phi_Emab)))) return S_5_Emabcd #------------------------------------------------------------------------- # Evaluation - get the corrector and predictor #------------------------------------------------------------------------- def get_corr_pred(self, eps_Emab_n1, deps_Emab, tn, tn1, update_state, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna): if update_state: eps_Emab_n = eps_Emab_n1 - deps_Emab eps_N_Emn = self._get_e_N_Emn_2(eps_Emab_n) eps_T_Emna = self._get_e_T_Emna_2(eps_Emab_n) w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn = self.get_normal_law( eps_N_Emn, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn) w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna = self.get_tangential_law( eps_T_Emna, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) phi_Emn = self._get_phi_Emn(eps_Emab_n1, w_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, w_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) phi_Emab = self._get_phi_Emab(phi_Emn) D_Emabcd = self._get_S_4_Emabcd(eps_Emab_n1, phi_Emab) sig_Emab = einsum('Emabcd,Emcd -> Emab', D_Emabcd, eps_Emab_n1) return D_Emabcd, sig_Emab #----------------------------------------------- # number of microplanes - currently fixed for 3D #----------------------------------------------- n_mp = Constant(28) #----------------------------------------------- # get the normal vectors of the microplanes #----------------------------------------------- _MPN = Property(depends_on='n_mp') @cached_property def _get__MPN(self): return array([[.577350259, .577350259, .577350259], [.577350259, .577350259, -.577350259], [.577350259, -.577350259, .577350259], [.577350259, -.577350259, -.577350259], [.935113132, .250562787, .250562787], [.935113132, .250562787, -.250562787], [.935113132, -.250562787, .250562787], [.935113132, -.250562787, -.250562787], [.250562787, .935113132, .250562787], [.250562787, .935113132, -.250562787], [.250562787, -.935113132, .250562787], [.250562787, -.935113132, -.250562787], [.250562787, .250562787, .935113132], [.250562787, .250562787, -.935113132], [.250562787, -.250562787, .935113132], [.250562787, -.250562787, -.935113132], [.186156720, .694746614, .694746614], [.186156720, .694746614, -.694746614], [.186156720, -.694746614, .694746614], [.186156720, -.694746614, -.694746614], [.694746614, .186156720, .694746614], [.694746614, .186156720, -.694746614], [.694746614, -.186156720, .694746614], [.694746614, -.186156720, -.694746614], [.694746614, .694746614, .186156720], [.694746614, .694746614, -.186156720], [.694746614, -.694746614, .186156720], [.694746614, -.694746614, -.186156720]]) #------------------------------------- # get the weights of the microplanes #------------------------------------- _MPW = Property(depends_on='n_mp') @cached_property def _get__MPW(self): return array([ .0160714276, .0160714276, .0160714276, .0160714276, .0204744730, .0204744730, .0204744730, .0204744730, .0204744730, .0204744730, .0204744730, .0204744730, .0204744730, .0204744730, .0204744730, .0204744730, .0158350505, .0158350505, .0158350505, .0158350505, .0158350505, .0158350505, .0158350505, .0158350505, .0158350505, .0158350505, .0158350505, .0158350505 ]) * 6.0
class FETS2DMITC(FETSEval): r'''MITC3 shell finite element: See https://www.sesamx.io/blog/standard_linear_triangular_shell_element/ See http://dx.doi.org/10.1016/j.compstruc.2014.02.005 ''' # vtk_r = tr.Array(np.float_, value=[[1, 0, 0], [0, 1, 0], [0, 0, 1]]) # vtk_cells = [[0, 1, 2]] # vtk_cell_types = 'Triangle' # vtk_cell = [0, 1, 2] # vtk_cell_type = 'Triangle' # vtk_expand_operator = tr.Array(np.float_, value=DELTA23_ab) # ========================================================================= # Surface integrals using numerical integration # ========================================================================= # j is gauss point index, p is point coords in natural coords (zeta_1, zeta_2, zeta_3) eta_jp = tr.Array('float_') r'''Integration points within a triangle. ''' def _eta_jp_default(self): # We applay the 7-point Gauss integration to integrate exactly on the plane defined by rr and ss # [r, s, t] at each Gauss point (r, s, t) are natural coords in the shell element r,s in plane # and t along thickness # 2 Gauss points along thickness t # 7 Gauss points on the plane of the element return np.array( [[1. / 3., 1. / 3., 1. / 3.]], dtype='f' ) # should be return np.array([[1. / 3., 1. / 3., 0]], dtype='f') w_m = tr.Array('float_') r'''Weight factors for numerical integration.''' def _w_m_default(self): print('w_m called!!') return np.array([1], dtype='f') # TODO, different node thickness in each node according to the original # implementation can be easily integrated, but here the same thickness # is used for simplicity. a = tr.Float(1.0, label='thickness') n_m = tr.Property(depends_on='w_m') r'''Number of integration points.''' @tr.cached_property def _get_n_m(self): return len(self.w_m) n_nodal_dofs = tr.Int(5) dh_imr = tr.Property(depends_on='eta_jp') r'''Derivatives of the shape functions in the integration points.''' @tr.cached_property def _get_dh_imr(self): # Same for all Gauss points dh_ri = np.array( [ [1, 0, -1], # dh1/d_r, dh2/d_r, dh3/d_r [0, 1, -1], # dh1/d_s, dh2/d_s, dh3/d_s [0, 0, 0] ], # dh1/d_t, dh2/d_t, dh3/d_t dtype=np.float_) dh_mri = np.tile(dh_ri, (self.n_m, 1, 1)) # dh_mri = np.flip(dh_mri, 2) print('dh_imr called!') return np.einsum('mri->imr', dh_mri) dht_imr = tr.Property(depends_on='eta_jp') r'''Derivatives of the (shape functions * t) in the integration points. ''' @tr.cached_property def _get_dht_imr(self): # m: gauss points, r: r, s, t, and i: h1, h2, h3 eta_jp = self.eta_jp dht_mri = np.array( [ [ [t, 0, -t], # (t*dh1)/d_r, (t*dh2)/d_r, (t*dh3)/d_r [0, t, -t], # (t*dh1)/d_s, (t*dh2)/d_s, (t*dh3)/d_s [r, s, 1 - r - s] ] # (t*dh1)/d_t, (t*dh2)/d_t, (t*dh3)/d_t for r, s, t in zip(eta_jp[:, 0], eta_jp[:, 1], eta_jp[:, 2]) ], dtype=np.float_) # dht_mri = np.flip(dht_mri, 2) return np.einsum('mri->imr', dht_mri)
class SlideExplorer(bu.Model): name = 'Explorer' tree = [ 'slide_model', 'inel_state_evolution', 'energy_dissipation', 'tf_s_x', 'tf_s_y', 'tf_w' ] slide_model = bu.Instance(Slide32, (), tree=True) # slide_model = bu.Instance(Slide34, (), tree=True) energy_dissipation = bu.Instance(EnergyDissipation, tree=True) '''Viewer to the energy dissipation''' def _energy_dissipation_default(self): return EnergyDissipation(slider_exp=self) inel_state_evolution = bu.Instance(InelStateEvolution, tree=True) '''Viewer to the inelastic state evolution''' def _inel_state_evolution_default(self): return InelStateEvolution(slider_exp=self) time_fn = bu.Instance(TimeFunction, (), tree=True) def __init__(self, *args, **kw): super(SlideExplorer, self).__init__(*args, **kw) self.reset_i() n_Eps = tr.Property() def _get_n_Eps(self): return len(self.slide_model.symb.Eps) s_x_1 = bu.Float(0, INC=True) s_y_1 = bu.Float(0, INC=True) w_1 = bu.Float(0, INC=True) tf_s_x = bu.Instance(TimeFunction, TIME=True) def _tf_s_x_default(self): return TFSelector() tf_s_y = bu.Instance(TimeFunction, TIME=True) def _tf_s_y_default(self): return TFSelector() tf_w = bu.Instance(TimeFunction, TIME=True) def _tf_w_default(self): return TFSelector() n_steps = bu.Int(10, ALG=True) k_max = bu.Int(20, ALG=True) Sig_arr = tr.Array Eps_arr = tr.Array Sig_t = tr.Property def _get_Sig_t(self): return self.Sig_arr Eps_t = tr.Property def _get_Eps_t(self): return self.Eps_arr ipw_view = bu.View(bu.Item('s_x_1', latex=r's_x'), bu.Item('s_y_1', latex=r's_y'), bu.Item('w_1', latex=r'w'), bu.Item('n_steps'), bu.Item('k_max'), bu.Item('t_max', readonly=True), time_editor=bu.ProgressEditor( run_method='run', reset_method='reset', interrupt_var='sim_stop', time_var='t', time_max='t_max', )) def reset_i(self): self.s_x_0, self.s_y_0, self.w_0 = 0, 0, 0 self.t0 = 0 self.t = 0 self.t_max = 1 self.Sig_arr = np.zeros((0, self.n_Eps)) self.Eps_arr = np.zeros((0, self.n_Eps)) self.Sig_record = [] self.Eps_record = [] self.iter_record = [] self.t_arr = [] self.s_x_t, self.s_y_t, self.w_t = [], [], [] self.Eps_n1 = np.zeros((self.n_Eps, ), dtype=np.float_) self.Sig_n1 = np.zeros((self.n_Eps, ), dtype=np.float_) self.s_x_1 = 0 self.s_y_1 = 0 self.w_1 = 0 t = bu.Float(0) t_max = bu.Float(1) def _t_max_changed(self): self.inel_state_evolution.t_max = self.t_max def get_response_i(self, update_progress=lambda t: t): # global Eps_record, Sig_record, iter_record # global t_arr, s_x_t, s_y_t, w_t, s_x_0, s_y_0, w_0, t0, Eps_n1 n_steps = self.n_steps t_i = np.linspace(0, 1, n_steps + 1) t1 = self.t0 + 1 self.t_max = t1 ti_arr = np.linspace(self.t0, t1, n_steps + 1) delta_t = t1 - self.t0 tf_s_x = self.tf_s_x(np.linspace(0, delta_t, n_steps + 1)) tf_s_y = self.tf_s_y(np.linspace(0, delta_t, n_steps + 1)) tf_w = self.tf_w(np.linspace(0, delta_t, n_steps + 1)) # si_x_t = tf_s_x * np.linspace(self.s_x_0, self.s_x_1, n_steps + 1) + 1e-9 # si_y_t = tf_s_y * np.linspace(self.s_y_0, self.s_y_1, n_steps + 1) + 1e-9 # wi_t = tf_w * np.linspace(self.w_0, self.w_1, n_steps + 1) + 1e-9 si_x_t = self.s_x_0 + tf_s_x * (self.s_x_1 - self.s_x_0) + 1e-9 si_y_t = self.s_y_0 + tf_s_y * (self.s_y_1 - self.s_y_0) + 1e-9 wi_t = self.w_0 + tf_w * (self.w_1 - self.w_0) + 1e-9 for i, (t, s_x_n1, s_y_n1, w_n1) in enumerate(zip(t_i, si_x_t, si_y_t, wi_t)): if self.slide_model.debug_level == 1: print('============= INCREMENT', i) try: self.Eps_n1, self.Sig_n1, k = self.slide_model.get_sig_n1( s_x_n1, s_y_n1, w_n1, self.Sig_n1, self.Eps_n1, self.k_max) except ConvergenceError as e: print(e) break self.Sig_record.append(self.Sig_n1) self.Eps_record.append(self.Eps_n1) self.iter_record.append(k) self.t = t self.Sig_arr = np.array(self.Sig_record, dtype=np.float_) self.Eps_arr = np.array(self.Eps_record, dtype=np.float_) self.iter_t = np.array(self.iter_record, dtype=np.int_) n_i = len(self.iter_t) self.t_arr = np.hstack([self.t_arr, ti_arr])[:n_i] self.s_x_t = np.hstack([self.s_x_t, si_x_t])[:n_i] self.s_y_t = np.hstack([self.s_y_t, si_y_t])[:n_i] self.w_t = np.hstack([self.w_t, wi_t])[:n_i] self.t0 = t1 self.s_x_0, self.s_y_0, self.w_0 = self.s_x_1, self.s_y_1, self.w_1 # set the last step index in the response browser self.inel_state_evolution.t_max = self.t_arr[-1] return # ## Plotting functions # To simplify postprocessing examples, here are two aggregate plotting functions, one for the state and force variables, the other one for the evaluation of energies def plot_sig_w(self, ax): sig_t = self.Sig_arr.T[2, ...] ax.plot(self.w_t, sig_t, color='orange', lw=3) def plot3d_Sig_Eps(self, ax3d): tau_x, tau_y = self.Sig_arr.T[:2, ...] tau = np.sqrt(tau_x**2 + tau_y**2) ax3d.plot3D(self.s_x_t, self.s_y_t, tau, color='orange', lw=2) def run(self, update_progress=lambda t: t): try: self.get_response_i(update_progress) except ValueError: print('No convergence reached') return def reset(self): self.reset_i() def subplots(self, fig): ax = fig.add_gridspec(1, 3) ax1 = fig.add_subplot(ax[0, 0:2], projection='3d') ax2 = fig.add_subplot(ax[0:, -1]) return ax1, ax2 def update_plot(self, axes): ax_sxy, ax_sig = axes self.plot_sig_w(ax_sig) ax_sig.set_xlabel(r'$w$ [mm]') ax_sig.set_ylabel(r'$\sigma$ [MPa]') # plot_tau_s(ax1, Eps_arr[-1,...],s_max,500,get_g3,**kw) ax_sxy.plot(self.s_x_t, self.s_y_t, 0, color='red', lw=1) self.plot3d_Sig_Eps(ax_sxy) ax_sxy.set_xlabel(r'$s_x$ [mm]') ax_sxy.set_ylabel(r'$s_y$ [mm]') ax_sxy.set_zlabel(r'$\| \tau \| = \sqrt{\tau_x^2 + \tau_y^2}$ [MPa]')
class Slide34(MATSEval, bu.InjectSymbExpr): name = 'Slide 3.4' symb_class = Slide34Expr E_T = bu.Float(28000, MAT=True) gamma_T = bu.Float(10, MAT=True) K_T = bu.Float(8, MAT=True) S_T = bu.Float(1, MAT=True) c_T = bu.Float(1, MAT=True) bartau = bu.Float(28000, MAT=True) E_N = bu.Float(28000, MAT=True) S_N = bu.Float(1, MAT=True) c_N = bu.Float(1, MAT=True) m = bu.Float(0.1, MAT=True) f_t = bu.Float(3, MAT=True) f_c = bu.Float(30, MAT=True) f_c0 = bu.Float(20, MAT=True) eta = bu.Float(0.5, MAT=True) r = bu.Float(1, MAT=True) c_NT = tr.Property(bu.Float, depends_on='state_changed') @tr.cached_property def _get_c_NT(self): return np.sqrt(self.c_N * self.c_T) S_NT = tr.Property(bu.Float, depends_on='state_changed') @tr.cached_property def _get_S_NT(self): return np.sqrt(self.S_N * self.S_T) debug = bu.Bool(False) def C_codegen(self): import os import os.path as osp C_code = [] for symb_name, symb_params in self.symb.symb_expressions: c_func_name = 'get_' + symb_name c_func = ccode(c_func_name, getattr(self.symb, symb_name), 'SLIDE33') C_code.append(c_func) code_dirname = 'sympy_codegen' code_fname = 'SLIDE33_3D' home_dir = osp.expanduser('~') code_dir = osp.join(home_dir, code_dirname) if not osp.exists(code_dir): os.makedirs(code_dir) code_file = osp.join(code_dir, code_fname) print('generated code_file', code_file) h_file = code_file + '.h' c_file = code_file + '.c' h_f = open(h_file, 'w') c_f = open(c_file, 'w') if True: for function_C in C_code: h_f.write(function_C[1][1]) c_f.write(function_C[0][1]) h_f.close() c_f.close() ipw_view = bu.View( bu.Item('E_T', latex='E_T'), bu.Item('S_T'), bu.Item('c_T'), bu.Item('gamma_T'), bu.Item('K_T'), bu.Item('bartau', latex=r'\bar{\tau}'), bu.Item('E_N'), bu.Item('S_N'), bu.Item('c_N'), bu.Item('m'), bu.Item('f_t'), bu.Item('f_c', latex=r'f_\mathrm{c}'), bu.Item('f_c0', latex=r'f_\mathrm{c0}'), bu.Item('eta'), bu.Item('r'), bu.Item('c_NT', readonly=True), bu.Item('S_NT', readonly=True), ) damage_interaction = tr.Enum('final', 'geometric', 'arithmetic') get_phi_ = tr.Property def _get_get_phi_(self): return self.symb.get_phi_final_ get_Phi_ = tr.Property def _get_get_Phi_(self): return self.symb.get_Phi_final_ def get_f_df(self, u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k): if self.debug: print('w_n1', u_N_n1.dtype, u_N_n1.shape) print('s_x_n1', u_Tx_n1.dtype, u_Tx_n1.shape) print('s_y_n1', u_Ty_n1.dtype, u_Ty_n1.shape) print('Eps_k', Eps_k.dtype, Eps_k.shape) print('Sig_k', Sig_k.dtype, Sig_k.shape) ONES = np.ones_like(u_Tx_n1, dtype=np.float_) if self.debug: print('ONES', ONES.dtype) ZEROS = np.zeros_like(u_Tx_n1, dtype=np.float_) if self.debug: print('ZEROS', ZEROS.dtype) Sig_k = self.symb.get_Sig_(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k)[0] if self.debug: print('Sig_k', Sig_k.dtype, Sig_k.shape) dSig_dEps_k = self.symb.get_dSig_dEps_(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k, ZEROS, ONES) if self.debug: print('dSig_dEps_k', dSig_dEps_k.dtype) H_sig_pi = self.symb.get_H_sig_pi_(Sig_k) if self.debug: print('H_sig_pi', H_sig_pi.dtype) f_k = np.array([self.symb.get_f_(Eps_k, Sig_k, H_sig_pi)]) if self.debug: print('f_k', f_k.dtype) df_dSig_k = self.symb.get_df_dSig_(Eps_k, Sig_k, H_sig_pi, ZEROS, ONES) if self.debug: print('df_dSig_k', df_dSig_k.dtype) ddf_dEps_k = self.symb.get_ddf_dEps_(Eps_k, Sig_k, H_sig_pi, ZEROS, ONES) if self.debug: print('ddf_dEps_k', ddf_dEps_k.dtype) df_dEps_k = np.einsum('ik...,ji...->jk...', df_dSig_k, dSig_dEps_k) + ddf_dEps_k Phi_k = self.get_Phi_(Eps_k, Sig_k, H_sig_pi, ZEROS, ONES) dEps_dlambda_k = Phi_k df_dlambda = np.einsum('ki...,kj...->ij...', df_dEps_k, dEps_dlambda_k) df_k = df_dlambda return f_k, df_k, Sig_k def get_Eps_k1(self, u_N_n1, u_Tx_n1, u_Ty_n1, Eps_n, lam_k, Sig_k, Eps_k): '''Evolution equations: The update of state variables for an updated $\lambda_k$ is performed using this procedure. ''' ONES = np.ones_like(u_Tx_n1) ZEROS = np.zeros_like(u_Tx_n1) Sig_k = self.symb.get_Sig_(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k)[0] H_sig_pi = self.symb.get_H_sig_pi_(Sig_k) Phi_k = self.get_Phi_(Eps_k, Sig_k, H_sig_pi, ZEROS, ONES) Eps_k1 = Eps_n + lam_k * Phi_k[:, 0] return Eps_k1 rtol = bu.Float(1e-3, ALG=True) '''Relative tolerance of the return mapping algorithm related to the tensile strength ''' Eps_names = tr.Property @tr.cached_property def _get_Eps_names(self): return [eps.codename for eps in self.symb.Eps] Sig_names = tr.Property @tr.cached_property def _get_Sig_names(self): return [sig.codename for sig in self.symb.Sig] state_var_shapes = tr.Property @tr.cached_property def _get_state_var_shapes(self): '''State variables shapes: variables are using the codename string in the Cymbol definition Since the same string is used in the lambdify method via print_Symbol method defined in Cymbol as well''' return {eps_name: () for eps_name in self.Eps_names + self.Sig_names} k_max = bu.Int(100, ALG=True) '''Maximum number of iterations''' def get_corr_pred(self, eps_Ema, t_n1, **state): '''Return mapping iteration: This function represents a user subroutine in a finite element code or in a lattice model. The input is $s_{n+1}$ and the state variables representing the state in the previous solved step $\boldsymbol{\mathcal{E}}_n$. The procedure returns the stresses and state variables of $\boldsymbol{\mathcal{S}}_{n+1}$ and $\boldsymbol{\mathcal{E}}_{n+1}$ ''' eps_aEm = np.einsum('...a->a...', eps_Ema) dim = len(eps_aEm) if dim == 2: # hack - only one slip considered - 2D version select_idx = (1, 0) u_Tx_n1, u_N_n1 = eps_aEm u_Ty_n1 = np.zeros_like(u_Tx_n1) else: raise ValueError('3D not implemented here') ONES = np.ones_like(u_Tx_n1, dtype=np.float_) if self.debug: print('ONES', ONES.dtype) ZEROS = np.zeros_like(u_Tx_n1, dtype=np.float_) if self.debug: print('ZEROS', ZEROS.dtype) # Transform state to Eps_k and Sig_k Eps_n = np.array([state[eps_name] for eps_name in self.Eps_names], dtype=np.float_) Eps_k = np.copy(Eps_n) #Sig_k = np.array([state[sig_name] for sig_name in self.Sig_names], dtype=np.float_) Sig_k = np.zeros_like(Eps_k) f_k, df_k, Sig_k = self.get_f_df(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k) f_k, df_k = f_k[0, ...], df_k[0, 0, ...] f_k_trial = f_k # indexes of inelastic entries L = np.where(f_k_trial > 0) # f norm in inelastic entries - to allow also positive values less the rtol f_k_norm_I = np.fabs(f_k_trial[L]) lam_k = np.zeros_like(f_k_trial) k = 0 while k < self.k_max: if self.debug: print('k', k) # which entries are above the tolerance I = np.where(f_k_norm_I > (self.f_t * self.rtol)) if self.debug: print('f_k_norm_I', f_k_norm_I, self.f_t * self.rtol, len(I[0])) if (len(I[0]) == 0): # empty inelastic entries - accept state #return Eps_k, Sig_k, k + 1 dSig_dEps_k = self.symb.get_dSig_dEps_(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k, ZEROS, ONES) ix1, ix2 = np.ix_(select_idx, select_idx) D_ = np.einsum('ab...->...ab', dSig_dEps_k[ix1, ix2, ...]) sig_ = np.einsum('a...->...a', Sig_k[select_idx, ...]) # quick fix _, _, _, _, _, _, omega_T, omega_N = Eps_k D_ = np.zeros(sig_.shape + (sig_.shape[-1], )) D_[..., 0, 0] = self.E_N * (1 - omega_N) D_[..., 1, 1] = self.E_T * (1 - omega_T) if dim == 3: D_[..., 2, 2] = self.E_T #* (1 - omega_T) for eps_name, Eps_ in zip(self.Eps_names, Eps_k): state[eps_name][...] = Eps_[...] for sig_name, Sig_ in zip(self.Sig_names, Sig_k): state[sig_name][...] = Sig_[...] return sig_, D_ if self.debug: print('I', I) print('L', L) LL = tuple(Li[I] for Li in L) L = LL if self.debug: print('new L', L) print('f_k', f_k[L].shape, f_k[L].dtype) print('df_k', df_k[L].shape, df_k[L].dtype) # return mapping on inelastic entries dlam_L = -f_k[L] / df_k[L] # np.linalg.solve(df_k[I], -f_k[I]) if self.debug: print('dlam_I', dlam_L, dlam_L.dtype) lam_k[L] += dlam_L if self.debug: print('lam_k_L', lam_k, lam_k.dtype, lam_k[L].shape) L_slice = (slice(None), ) + L Eps_k_L = self.get_Eps_k1(u_N_n1[L], u_Tx_n1[L], u_Ty_n1[L], Eps_n[L_slice], lam_k[L], Sig_k[L_slice], Eps_k[L_slice]) Eps_k[L_slice] = Eps_k_L f_k_L, df_k_L, Sig_k_L = self.get_f_df(u_N_n1[L], u_Tx_n1[L], u_Ty_n1[L], Sig_k[L_slice], Eps_k_L) f_k[L], df_k[L] = f_k_L[0, ...], df_k_L[0, 0, ...] Sig_k[L_slice] = Sig_k_L if self.debug: print('Sig_k', Sig_k) print('f_k', f_k) f_k_norm_I = np.fabs(f_k[L]) k += 1 else: raise ConvergenceError('no convergence for entries', [L, u_N_n1[I], u_Tx_n1[I], u_Ty_n1[I]]) # add the algorithmic stiffness # recalculate df_k and -f_k for a unit increment of epsilon and solve for lambda # def plot_f_state(self, ax, Eps, Sig): lower = -self.f_c * 1.05 upper = self.f_t + 0.05 * self.f_c lower_tau = -self.bartau * 2 upper_tau = self.bartau * 2 lower_tau = 0 upper_tau = 10 sig, tau_x, tau_y = Sig[:3] tau = np.sqrt(tau_x**2 + tau_y**2) sig_ts, tau_x_ts = np.mgrid[lower:upper:201j, lower_tau:upper_tau:201j] Sig_ts = np.zeros((len(self.symb.Eps), ) + tau_x_ts.shape) Eps_ts = np.zeros_like(Sig_ts) Sig_ts[0, ...] = sig_ts Sig_ts[1, ...] = tau_x_ts Sig_ts[3:, ...] = Sig[3:, np.newaxis, np.newaxis] Eps_ts[...] = Eps[:, np.newaxis, np.newaxis] H_sig_pi = self.symb.get_H_sig_pi_(Sig_ts) f_ts = np.array([self.symb.get_f_(Eps_ts, Sig_ts, H_sig_pi)]) #phi_ts = np.array([self.symb.get_phi_(Eps_ts, Sig_ts)]) ax.set_title('threshold function') omega_N = Eps_ts[-1, :] omega_T = Eps_ts[-2, :] sig_ts_eff = sig_ts / (1 - H_sig_pi * omega_N) tau_x_ts_eff = tau_x_ts / (1 - omega_T) ax.contour(sig_ts_eff, tau_x_ts_eff, f_ts[0, ...], levels=0, colors=('green', )) ax.contour(sig_ts, tau_x_ts, f_ts[0, ...], levels=0, colors=('red', )) #ax.contour(sig_ts, tau_x_ts, phi_ts[0, ...]) ax.plot(sig, tau, marker='H', color='red') ax.plot([lower, upper], [0, 0], color='black', lw=0.4) ax.plot([0, 0], [lower_tau, upper_tau], color='black', lw=0.4) ax.set_ylim(ymin=0, ymax=10) def plot_f(self, ax): lower = -self.f_c * 1.05 upper = self.f_t + 0.05 * self.f_c lower_tau = -self.bartau * 2 upper_tau = self.bartau * 2 sig_ts, tau_x_ts = np.mgrid[lower:upper:201j, lower_tau:upper_tau:201j] Sig_ts = np.zeros((len(self.symb.Eps), ) + tau_x_ts.shape) Sig_ts[0, :] = sig_ts Sig_ts[1, :] = tau_x_ts Eps_ts = np.zeros_like(Sig_ts) H_sig_pi = self.symb.get_H_sig_pi_(Sig_ts) f_ts = np.array([self.symb.get_f_(Eps_ts, Sig_ts, H_sig_pi)]) phi_ts = np.array([self.get_phi_(Eps_ts, Sig_ts, H_sig_pi)]) ax.set_title('threshold function') ax.contour(sig_ts, tau_x_ts, f_ts[0, ...], levels=0) ax.contour(sig_ts, tau_x_ts, phi_ts[0, ...]) ax.plot([lower, upper], [0, 0], color='black', lw=0.4) ax.plot([0, 0], [lower_tau, upper_tau], color='black', lw=0.4) def plot_sig_w(self, ax): pass def plot_tau_s(self, ax): pass def subplots(self, fig): return fig.subplots(2, 2) def update_plot(self, axes): (ax_sig_w, ax_tau_s), (ax_f, _) = axes self.plot_sig_w(ax_sig_w) self.plot_tau_s(ax_tau_s) self.plot_f(ax_f)
class Parameter(t.HasTraits): """Model parameter Attributes ---------- value : float or array The value of the parameter for the current location. The value for other locations is stored in map. bmin, bmax: float Lower and upper bounds of the parameter value. twin : {None, Parameter} If it is not None, the value of the current parameter is a function of the given Parameter. The function is by default the identity function, but it can be defined by twin_function twin_function_expr: str Expression of the ``twin_function`` that enables setting a functional relationship between the parameter and its twin. If ``twin`` is not ``None``, the parameter value is calculated as the output of calling the twin function with the value of the twin parameter. The string is parsed using sympy, so permitted values are any valid sympy expressions of one variable. If the function is invertible the twin inverse function is set automatically. twin_inverse_function : str Expression of the ``twin_inverse_function`` that enables setting the value of the twin parameter. If ``twin`` is not ``None``, its value is set to the output of calling the twin inverse function with the value provided. The string is parsed using sympy, so permitted values are any valid sympy expressions of one variable. twin_function : function **Setting this attribute manually is deprecated in HyperSpy newer than 1.1.2. It will become private in HyperSpy 2.0. Please use ``twin_function_expr`` instead.** twin_inverse_function : function **Setting this attribute manually is deprecated in HyperSpy newer than 1.1.2. It will become private in HyperSpy 2.0. Please use ``twin_inverse_function_expr`` instead.** ext_force_positive : bool If True, the parameter value is set to be the absolute value of the input value i.e. if we set Parameter.value = -3, the value stored is 3 instead. This is useful to bound a value to be positive in an optimization without actually using an optimizer that supports bounding. ext_bounded : bool Similar to ext_force_positive, but in this case the bounds are defined by bmin and bmax. It is a better idea to use an optimizer that supports bounding though. Methods ------- as_signal(field = 'values') Get a parameter map as a signal object plot() Plots the value of the Parameter at all locations. export(folder=None, name=None, format=None, save_std=False) Saves the value of the parameter map to the specified format connect, disconnect(function) Call the functions connected when the value attribute changes. """ __number_of_elements = 1 __value = 0 __free = True _bounds = (None, None) __twin = None _axes_manager = None __ext_bounded = False __ext_force_positive = False # traitsui bugs out trying to make an editor for this, so always specify! # (it bugs out, because both editor shares the object, and Array editors # don't like non-sequence objects). TextEditor() works well, so does # RangeEditor() as it works with bmin/bmax. value = t.Property(t.Either([t.CFloat(0), Array()])) units = t.Str('') free = t.Property(t.CBool(True)) bmin = t.Property(NoneFloat(), label="Lower bounds") bmax = t.Property(NoneFloat(), label="Upper bounds") _twin_function_expr = "" _twin_inverse_function_expr = "" twin_function = None _twin_inverse_function = None _twin_inverse_sympy = None def __init__(self): self._twins = set() self.events = Events() self.events.value_changed = Event(""" Event that triggers when the `Parameter.value` changes. The event triggers after the internal state of the `Parameter` has been updated. Arguments --------- obj : Parameter The `Parameter` that the event belongs to value : {float | array} The new value of the parameter """, arguments=["obj", 'value']) self.std = None self.component = None self.grad = None self.name = '' self.units = '' self.map = None self.model = None self._whitelist = { '_id_name': None, 'value': None, 'std': None, 'free': None, 'units': None, 'map': None, '_bounds': None, 'ext_bounded': None, 'name': None, 'ext_force_positive': None, 'twin_function_expr': None, 'twin_inverse_function_expr': None, 'self': ('id', None), } self._slicing_whitelist = {'map': 'inav'} def _load_dictionary(self, dictionary): """Load data from dictionary Parameters ---------- dict : dictionary A dictionary containing at least the following items: _id_name : string _id_name of the original parameter, used to create the dictionary. Has to match with the self._id_name _whitelist : dictionary a dictionary, which keys are used as keywords to match with the parameter attributes. For more information see :meth:`hyperspy.misc.export_dictionary.load_from_dictionary` * any field from _whitelist.keys() * Returns ------- id_value : int the ID value of the original parameter, to be later used for setting up the correct twins """ if dictionary['_id_name'] == self._id_name: load_from_dictionary(self, dictionary) return dictionary['self'] else: raise ValueError( "_id_name of parameter and dictionary do not match, \nparameter._id_name = %s\ \ndictionary['_id_name'] = %s" % (self._id_name, dictionary['_id_name'])) def __repr__(self): text = '' text += 'Parameter %s' % self.name if self.component is not None: text += ' of %s' % self.component._get_short_description() text = '<' + text + '>' return text def __len__(self): return self._number_of_elements @property def twin_function_expr(self): return self._twin_function_expr @twin_function_expr.setter def twin_function_expr(self, value): if not value: self.twin_function = None self.twin_inverse_function = None self._twin_function_expr = "" self._twin_inverse_sympy = None return expr = sympy.sympify(value) if len(expr.free_symbols) > 1: raise ValueError("The expression must contain only one variable.") elif len(expr.free_symbols) == 0: raise ValueError("The expression must contain one variable, " "it contains none.") x = tuple(expr.free_symbols)[0] self.twin_function = lambdify(x, expr.evalf()) self._twin_function_expr = value if not self.twin_inverse_function: y = sympy.Symbol(x.name + "2") try: inv = sympy.solveset(sympy.Eq(y, expr), x) self._twin_inverse_sympy = lambdify(y, inv) self._twin_inverse_function = None except BaseException: # Not all may have a suitable solution. self._twin_inverse_function = None self._twin_inverse_sympy = None _logger.warning( "The function {} is not invertible. Setting the value of " "{} will raise an AttributeError unless you set manually " "``twin_inverse_function_expr``. Otherwise, set the " "value of its twin parameter instead.".format(value, self)) @property def twin_inverse_function_expr(self): if self.twin: return self._twin_inverse_function_expr else: return "" @twin_inverse_function_expr.setter def twin_inverse_function_expr(self, value): if not value: self.twin_inverse_function = None self._twin_inverse_function_expr = "" return expr = sympy.sympify(value) if len(expr.free_symbols) > 1: raise ValueError("The expression must contain only one variable.") elif len(expr.free_symbols) == 0: raise ValueError("The expression must contain one variable, " "it contains none.") x = tuple(expr.free_symbols)[0] self._twin_inverse_function = lambdify(x, expr.evalf()) self._twin_inverse_function_expr = value @property def twin_inverse_function(self): if (not self.twin_inverse_function_expr and self.twin_function_expr and self._twin_inverse_sympy): return lambda x: self._twin_inverse_sympy(x).pop() else: return self._twin_inverse_function @twin_inverse_function.setter def twin_inverse_function(self, value): self._twin_inverse_function = value def _get_value(self): if self.twin is None: return self.__value else: if self.twin_function: return self.twin_function(self.twin.value) else: return self.twin.value def _set_value(self, value): try: # Use try/except instead of hasattr("__len__") because a numpy # memmap has a __len__ wrapper even for numbers that raises a # TypeError when calling. See issue #349. if len(value) != self._number_of_elements: raise ValueError("The length of the parameter must be ", self._number_of_elements) else: if not isinstance(value, tuple): value = tuple(value) except TypeError: if self._number_of_elements != 1: raise ValueError("The length of the parameter must be ", self._number_of_elements) old_value = self.__value if self.twin is not None: if self.twin_function is not None: if self.twin_inverse_function is not None: self.twin.value = self.twin_inverse_function(value) return else: raise AttributeError( "This parameter has a ``twin_function`` but" "its ``twin_inverse_function`` is not defined.") else: self.twin.value = value return if self.ext_bounded is False: self.__value = value else: if self.ext_force_positive is True: value = np.abs(value) if self._number_of_elements == 1: if self.bmin is not None and value <= self.bmin: self.__value = self.bmin elif self.bmax is not None and value >= self.bmax: self.__value = self.bmax else: self.__value = value else: bmin = (self.bmin if self.bmin is not None else -np.inf) bmax = (self.bmax if self.bmin is not None else np.inf) self.__value = np.clip(value, bmin, bmax) if (self._number_of_elements != 1 and not isinstance(self.__value, tuple)): self.__value = tuple(self.__value) if old_value != self.__value: self.events.value_changed.trigger(value=self.__value, obj=self) self.trait_property_changed('value', old_value, self.__value) # Fix the parameter when coupled def _get_free(self): if self.twin is None: return self.__free else: return False def _set_free(self, arg): old_value = self.__free self.__free = arg if self.component is not None: self.component._update_free_parameters() self.trait_property_changed('free', old_value, self.__free) def _on_twin_update(self, value, twin=None): if (twin is not None and hasattr(twin, 'events') and hasattr(twin.events, 'value_changed')): with twin.events.value_changed.suppress_callback( self._on_twin_update): self.events.value_changed.trigger(value=value, obj=self) else: self.events.value_changed.trigger(value=value, obj=self) def _set_twin(self, arg): if arg is None: if self.twin is not None: # Store the value of the twin in order to set the # value of the parameter when it is uncoupled twin_value = self.value if self in self.twin._twins: self.twin._twins.remove(self) self.twin.events.value_changed.disconnect( self._on_twin_update) self.__twin = arg self.value = twin_value else: if self not in arg._twins: arg._twins.add(self) arg.events.value_changed.connect(self._on_twin_update, ["value"]) self.__twin = arg if self.component is not None: self.component._update_free_parameters() def _get_twin(self): return self.__twin twin = property(_get_twin, _set_twin) def _get_bmin(self): if self._number_of_elements == 1: return self._bounds[0] else: return self._bounds[0][0] def _set_bmin(self, arg): old_value = self.bmin if self._number_of_elements == 1: self._bounds = (arg, self.bmax) else: self._bounds = ((arg, self.bmax), ) * self._number_of_elements # Update the value to take into account the new bounds self.value = self.value self.trait_property_changed('bmin', old_value, arg) def _get_bmax(self): if self._number_of_elements == 1: return self._bounds[1] else: return self._bounds[0][1] def _set_bmax(self, arg): old_value = self.bmax if self._number_of_elements == 1: self._bounds = (self.bmin, arg) else: self._bounds = ((self.bmin, arg), ) * self._number_of_elements # Update the value to take into account the new bounds self.value = self.value self.trait_property_changed('bmax', old_value, arg) @property def _number_of_elements(self): return self.__number_of_elements @_number_of_elements.setter def _number_of_elements(self, arg): # Do nothing if the number of arguments stays the same if self.__number_of_elements == arg: return if arg <= 1: raise ValueError("Please provide an integer number equal " "or greater to 1") self._bounds = ((self.bmin, self.bmax), ) * arg self.__number_of_elements = arg if arg == 1: self._Parameter__value = 0 else: self._Parameter__value = (0, ) * arg if self.component is not None: self.component.update_number_parameters() @property def ext_bounded(self): return self.__ext_bounded @ext_bounded.setter def ext_bounded(self, arg): if arg is not self.__ext_bounded: self.__ext_bounded = arg # Update the value to take into account the new bounds self.value = self.value @property def ext_force_positive(self): return self.__ext_force_positive @ext_force_positive.setter def ext_force_positive(self, arg): if arg is not self.__ext_force_positive: self.__ext_force_positive = arg # Update the value to take into account the new bounds self.value = self.value def store_current_value_in_array(self): """Store the value and std attributes. See also -------- fetch, assign_current_value_to_all """ indices = self._axes_manager.indices[::-1] # If it is a single spectrum indices is () if not indices: indices = (0, ) self.map['values'][indices] = self.value self.map['is_set'][indices] = True if self.std is not None: self.map['std'][indices] = self.std def fetch(self): """Fetch the stored value and std attributes. See Also -------- store_current_value_in_array, assign_current_value_to_all """ indices = self._axes_manager.indices[::-1] # If it is a single spectrum indices is () if not indices: indices = (0, ) if self.map['is_set'][indices]: value = self.map['values'][indices] std = self.map['std'][indices] if isinstance(value, dArray): value = value.compute() if isinstance(std, dArray): std = std.compute() self.value = value self.std = std def assign_current_value_to_all(self, mask=None): """Assign the current value attribute to all the indices Parameters ---------- mask: {None, boolean numpy array} Set only the indices that are not masked i.e. where mask is False. See Also -------- store_current_value_in_array, fetch """ if mask is None: mask = np.zeros(self.map.shape, dtype='bool') self.map['values'][mask == False] = self.value self.map['is_set'][mask == False] = True def _create_array(self): """Create the map array to store the information in multidimensional datasets. """ shape = self._axes_manager._navigation_shape_in_array if not shape: shape = [ 1, ] # Shape-1 fields in dtypes won’t be collapsed to scalars in a future # numpy version (see release notes numpy 1.17.0) if self._number_of_elements > 1: dtype_ = np.dtype([('values', 'float', self._number_of_elements), ('std', 'float', self._number_of_elements), ('is_set', 'bool')]) else: dtype_ = np.dtype([('values', 'float'), ('std', 'float'), ('is_set', 'bool')]) if (self.map is None or self.map.shape != shape or self.map.dtype != dtype_): self.map = np.zeros(shape, dtype_) self.map['std'].fill(np.nan) # TODO: in the future this class should have access to # axes manager and should be able to fetch its own # values. Until then, the next line is necessary to avoid # erros when self.std is defined and the shape is different # from the newly defined arrays self.std = None def as_signal(self, field='values'): """Get a parameter map as a signal object. Please note that this method only works when the navigation dimension is greater than 0. Parameters ---------- field : {'values', 'std', 'is_set'} Raises ------ NavigationDimensionError : if the navigation dimension is 0 """ from hyperspy.signal import BaseSignal s = BaseSignal(data=self.map[field], axes=self._axes_manager._get_navigation_axes_dicts()) if self.component is not None and \ self.component.active_is_multidimensional: s.data[np.logical_not(self.component._active_array)] = np.nan s.metadata.General.title = ("%s parameter" % self.name if self.component is None else "%s parameter of %s component" % (self.name, self.component.name)) for axis in s.axes_manager._axes: axis.navigate = False if self._number_of_elements > 1: s.axes_manager._append_axis(size=self._number_of_elements, name=self.name, navigate=True) s._assign_subclass() if field == "values": # Add the variance if available std = self.as_signal(field="std") if not np.isnan(std.data).all(): std.data = std.data**2 std.metadata.General.title = "Variance" s.metadata.set_item("Signal.Noise_properties.variance", std) return s def plot(self, **kwargs): """Plot parameter signal. Parameters ---------- **kwargs Any extra keyword arguments are passed to the signal plot. Example ------- >>> parameter.plot() #doctest: +SKIP Set the minimum and maximum displayed values >>> parameter.plot(vmin=0, vmax=1) #doctest: +SKIP """ self.as_signal().plot(**kwargs) def export(self, folder=None, name=None, format="hspy", save_std=False): """Save the data to a file. All the arguments are optional. Parameters ---------- folder : str or None The path to the folder where the file will be saved. If `None` the current folder is used by default. name : str or None The name of the file. If `None` the Components name followed by the Parameter `name` attributes will be used by default. If a file with the same name exists the name will be modified by appending a number to the file path. save_std : bool If True, also the standard deviation will be saved format: str The extension of any file format supported by HyperSpy, default hspy """ if format is None: format = "hspy" if name is None: name = self.component.name + '_' + self.name filename = incremental_filename(slugify(name) + '.' + format) if folder is not None: filename = os.path.join(folder, filename) self.as_signal().save(filename) if save_std is True: self.as_signal(field='std').save(append2pathname(filename, '_std')) def as_dictionary(self, fullcopy=True): """Returns parameter as a dictionary, saving all attributes from self._whitelist.keys() For more information see :meth:`hyperspy.misc.export_dictionary.export_to_dictionary` Parameters ---------- fullcopy : Bool (optional, False) Copies of objects are stored, not references. If any found, functions will be pickled and signals converted to dictionaries Returns ------- dic : dictionary with the following keys: _id_name : string _id_name of the original parameter, used to create the dictionary. Has to match with the self._id_name _twins : list a list of ids of the twins of the parameter _whitelist : dictionary a dictionary, which keys are used as keywords to match with the parameter attributes. For more information see :meth:`hyperspy.misc.export_dictionary.export_to_dictionary` * any field from _whitelist.keys() * """ dic = {'_twins': [id(t) for t in self._twins]} export_to_dictionary(self, self._whitelist, dic, fullcopy) return dic def default_traits_view(self): # As mentioned above, the default editor for # value = t.Property(t.Either([t.CFloat(0), Array()])) # gives a ValueError. We therefore implement default_traits_view so # that configure/edit_traits will still work straight out of the box. # A whitelist controls which traits to include in this view. from traitsui.api import RangeEditor, View, Item whitelist = ['bmax', 'bmin', 'free', 'name', 'std', 'units', 'value'] editable_traits = [ trait for trait in self.editable_traits() if trait in whitelist ] if 'value' in editable_traits: i = editable_traits.index('value') v = editable_traits.pop(i) editable_traits.insert( i, Item(v, editor=RangeEditor(low_name='bmin', high_name='bmax'))) view = View(editable_traits, buttons=['OK', 'Cancel']) return view
class Component(t.HasTraits): __axes_manager = None active = t.Property(t.CBool(True)) name = t.Property(t.Str('')) def __init__(self, parameter_name_list): self.events = Events() self.events.active_changed = Event(""" Event that triggers when the `Component.active` changes. The event triggers after the internal state of the `Component` has been updated. Arguments --------- obj : Component The `Component` that the event belongs to active : bool The new active state """, arguments=["obj", 'active']) self.parameters = [] self.init_parameters(parameter_name_list) self._update_free_parameters() self.active = True self._active_array = None self.isbackground = False self.convolved = True self.parameters = tuple(self.parameters) self._id_name = self.__class__.__name__ self._id_version = '1.0' self._position = None self.model = None self.name = '' self._whitelist = { '_id_name': None, 'name': None, 'active_is_multidimensional': None, '_active_array': None, 'active': None } self._slicing_whitelist = {'_active_array': 'inav'} self._slicing_order = ( 'active', 'active_is_multidimensional', '_active_array', ) _name = '' _active_is_multidimensional = False _active = True @property def active_is_multidimensional(self): return self._active_is_multidimensional @active_is_multidimensional.setter def active_is_multidimensional(self, value): if not isinstance(value, bool): raise ValueError('Only boolean values are permitted') if value == self.active_is_multidimensional: return if value: # Turn on if self._axes_manager.navigation_size < 2: _logger.info('`navigation_size` < 2, skipping') return # Store value at current position self._create_active_array() self._store_active_value_in_array(self._active) self._active_is_multidimensional = True else: # Turn off # Get the value at the current position before switching it off self._active = self.active self._active_array = None self._active_is_multidimensional = False def _get_name(self): return self._name def _set_name(self, value): old_value = self._name if old_value == value: return if self.model: for component in self.model: if value == component.name: raise ValueError("Another component already has " "the name " + str(value)) self._name = value setattr(self.model.components, slugify(value, valid_variable_name=True), self) self.model.components.__delattr__( slugify(old_value, valid_variable_name=True)) else: self._name = value self.trait_property_changed('name', old_value, self._name) @property def _axes_manager(self): return self.__axes_manager @_axes_manager.setter def _axes_manager(self, value): for parameter in self.parameters: parameter._axes_manager = value self.__axes_manager = value @property def _is_navigation_multidimensional(self): if (self._axes_manager is None or not self._axes_manager.navigation_dimension): return False else: return True def _get_active(self): if self.active_is_multidimensional is True: # The following should set self.active = self._active_array[self._axes_manager.indices[::-1]] return self._active def _store_active_value_in_array(self, value): self._active_array[self._axes_manager.indices[::-1]] = value def _set_active(self, arg): if self._active == arg: return old_value = self._active self._active = arg if self.active_is_multidimensional is True: self._store_active_value_in_array(arg) self.events.active_changed.trigger(active=self._active, obj=self) self.trait_property_changed('active', old_value, self._active) def init_parameters(self, parameter_name_list): for name in parameter_name_list: parameter = Parameter() self.parameters.append(parameter) parameter.name = name parameter._id_name = name setattr(self, name, parameter) if hasattr(self, 'grad_' + name): parameter.grad = getattr(self, 'grad_' + name) parameter.component = self self.add_trait(name, t.Instance(Parameter)) def _get_long_description(self): if self.name: text = '%s (%s component)' % (self.name, self.__class__.__name__) else: text = '%s component' % self.__class__.__name__ return text def _get_short_description(self): text = '' if self.name: text += self.name else: text += self.__class__.__name__ text += ' component' return text def __repr__(self): text = '<%s>' % self._get_long_description() return text def _update_free_parameters(self): self.free_parameters = sorted( [par for par in self.parameters if par.free], key=lambda x: x.name) self._nfree_param = sum( [par._number_of_elements for par in self.free_parameters]) def update_number_parameters(self): i = 0 for parameter in self.parameters: i += parameter._number_of_elements self.nparam = i self._update_free_parameters() def fetch_values_from_array(self, p, p_std=None, onlyfree=False): if onlyfree is True: parameters = self.free_parameters else: parameters = self.parameters i = 0 for parameter in sorted(parameters, key=lambda x: x.name): length = parameter._number_of_elements parameter.value = (p[i] if length == 1 else p[i:i + length]) if p_std is not None: parameter.std = (p_std[i] if length == 1 else tuple( p_std[i:i + length])) i += length def _create_active_array(self): shape = self._axes_manager._navigation_shape_in_array if len(shape) == 1 and shape[0] == 0: shape = [ 1, ] if (not isinstance(self._active_array, np.ndarray) or self._active_array.shape != shape): _logger.debug('Creating _active_array for {}.\n\tCurrent array ' 'is:\n{}'.format(self, self._active_array)) self._active_array = np.ones(shape, dtype=bool) def _create_arrays(self): if self.active_is_multidimensional: self._create_active_array() for parameter in self.parameters: parameter._create_array() def store_current_parameters_in_map(self): for parameter in self.parameters: parameter.store_current_value_in_array() def fetch_stored_values(self, only_fixed=False): if self.active_is_multidimensional: # Store the stored value in self._active and trigger the connected # functions. self.active = self.active if only_fixed is True: parameters = (set(self.parameters) - set(self.free_parameters)) else: parameters = self.parameters parameters = [ parameter for parameter in parameters if (parameter.twin is None or not isinstance(parameter.twin, Parameter)) ] for parameter in parameters: parameter.fetch() def plot(self, only_free=True): """Plot the value of the parameters of the model Parameters ---------- only_free : bool If True, only the value of the parameters that are free will be plotted """ if only_free: parameters = self.free_parameters else: parameters = self.parameters parameters = [k for k in parameters if k.twin is None] for parameter in parameters: parameter.plot() def export(self, folder=None, format="hspy", save_std=False, only_free=True): """Plot the value of the parameters of the model Parameters ---------- folder : str or None The path to the folder where the file will be saved. If `None` the current folder is used by default. format : str The extension of the file format, default "hspy". save_std : bool If True, also the standard deviation will be saved. only_free : bool If True, only the value of the parameters that are free will be exported. Notes ----- The name of the files will be determined by each the Component and each Parameter name attributes. Therefore, it is possible to customise the file names modify the name attributes. """ if only_free: parameters = self.free_parameters else: parameters = self.parameters parameters = [k for k in parameters if k.twin is None] for parameter in parameters: parameter.export( folder=folder, format=format, save_std=save_std, ) def summary(self): for parameter in self.parameters: dim = len(parameter.map.squeeze().shape) if parameter.map \ is not None else 0 if parameter.twin is None: if dim <= 1: print('%s = %s ± %s %s' % (parameter.name, parameter.value, parameter.std, parameter.units)) def __call__(self): """Returns the corresponding model for the current coordinates Returns ------- numpy array """ axis = self.model.axis.axis[self.model.channel_switches] component_array = self.function(axis) return component_array def _component2plot(self, axes_manager, out_of_range2nans=True): old_axes_manager = None if axes_manager is not self.model.axes_manager: old_axes_manager = self.model.axes_manager self.model.axes_manager = axes_manager self.fetch_stored_values() s = self.model.__call__(component_list=[self]) if not self.active: s.fill(np.nan) if old_axes_manager is not None: self.model.axes_manager = old_axes_manager self.charge() if out_of_range2nans is True: ns = np.empty(self.model.axis.axis.shape) ns.fill(np.nan) ns[self.model.channel_switches] = s s = ns if old_axes_manager is not None: self.model.axes_manager = old_axes_manager self.fetch_stored_values() return s def set_parameters_free(self, parameter_name_list=None): """ Sets parameters in a component to free. Parameters ---------- parameter_name_list : None or list of strings, optional If None, will set all the parameters to free. If list of strings, will set all the parameters with the same name as the strings in parameter_name_list to free. Examples -------- >>> v1 = hs.model.components1D.Voigt() >>> v1.set_parameters_free() >>> v1.set_parameters_free(parameter_name_list=['area','centre']) See also -------- set_parameters_not_free hyperspy.model.BaseModel.set_parameters_free hyperspy.model.BaseModel.set_parameters_not_free """ parameter_list = [] if not parameter_name_list: parameter_list = self.parameters else: for _parameter in self.parameters: if _parameter.name in parameter_name_list: parameter_list.append(_parameter) for _parameter in parameter_list: _parameter.free = True def set_parameters_not_free(self, parameter_name_list=None): """ Sets parameters in a component to not free. Parameters ---------- parameter_name_list : None or list of strings, optional If None, will set all the parameters to not free. If list of strings, will set all the parameters with the same name as the strings in parameter_name_list to not free. Examples -------- >>> v1 = hs.model.components1D.Voigt() >>> v1.set_parameters_not_free() >>> v1.set_parameters_not_free(parameter_name_list=['area','centre']) See also -------- set_parameters_free hyperspy.model.BaseModel.set_parameters_free hyperspy.model.BaseModel.set_parameters_not_free """ parameter_list = [] if not parameter_name_list: parameter_list = self.parameters else: for _parameter in self.parameters: if _parameter.name in parameter_name_list: parameter_list.append(_parameter) for _parameter in parameter_list: _parameter.free = False def _estimate_parameters(self, signal): self.binned = signal.metadata.Signal.binned if self._axes_manager != signal.axes_manager: self._axes_manager = signal.axes_manager self._create_arrays() def as_dictionary(self, fullcopy=True): """Returns component as a dictionary For more information on method and conventions, see :meth:`hyperspy.misc.export_dictionary.export_to_dictionary` Parameters ---------- fullcopy : Bool (optional, False) Copies of objects are stored, not references. If any found, functions will be pickled and signals converted to dictionaries Returns ------- dic : dictionary A dictionary, containing at least the following fields: parameters : list a list of dictionaries of the parameters, one per _whitelist : dictionary a dictionary with keys used as references saved attributes, for more information, see :meth:`hyperspy.misc.export_dictionary.export_to_dictionary` * any field from _whitelist.keys() * """ dic = { 'parameters': [p.as_dictionary(fullcopy) for p in self.parameters] } dic.update(get_object_package_info(self)) export_to_dictionary(self, self._whitelist, dic, fullcopy) from hyperspy.model import _COMPONENTS if self._id_name not in _COMPONENTS: import dill dic['_class_dump'] = dill.dumps(self.__class__) return dic def _load_dictionary(self, dic): """Load data from dictionary. Parameters ---------- dict : dictionary A dictionary containing following items: _id_name : string _id_name of the original component, used to create the dictionary. Has to match with the self._id_name parameters : list A list of dictionaries, one per parameter of the component (see parameter.as_dictionary() documentation for more) _whitelist : dictionary a dictionary, which keys are used as keywords to match with the component attributes. For more information see :meth:`hyperspy.misc.export_dictionary.load_from_dictionary` * any field from _whitelist.keys() * Returns ------- twin_dict : dictionary Dictionary of 'id' values from input dictionary as keys with all of the parameters of the component, to be later used for setting up correct twins. """ if dic['_id_name'] == self._id_name: if (self._id_name == "Polynomial" and LooseVersion(hyperspy.__version__) >= LooseVersion("2.0")): # in HyperSpy 2.0 the polynomial definition changed from hyperspy._components.polynomial import convert_to_polynomial dic = convert_to_polynomial(dic) load_from_dictionary(self, dic) id_dict = {} for p in dic['parameters']: idname = p['_id_name'] if hasattr(self, idname): par = getattr(self, idname) t_id = par._load_dictionary(p) id_dict[t_id] = par else: raise ValueError( "_id_name of parameters in component and dictionary do not match" ) return id_dict else: raise ValueError( "_id_name of component and dictionary do not match, \ncomponent._id_name = %s\ \ndictionary['_id_name'] = %s" % (self._id_name, dic['_id_name'])) def print_current_values(self, only_free=False, fancy=True): """Prints the current values of the component's parameters. Parameters ---------- only_free : bool If True, only free parameters will be printed. fancy : bool If True, attempts to print using html rather than text in the notebook. """ if fancy: display(current_component_values(self, only_free=only_free)) else: display_pretty(current_component_values(self, only_free=only_free))
class Slide34Expr(bu.SymbExpr): # control and state variables w, s_x, s_y, Eps, Sig = w, s_x, s_y, Eps, Sig # model parameters E_T = E_T gamma_T = gamma_T K_T = K_T S_T = S_T c_T = c_T bartau = bartau E_N = E_N S_N = S_N c_N = c_N f_t = f_t f_c = f_c f_c0 = f_c0 m = m eta = eta r = r H_switch = H_switch Sig_signs = Sig_signs c_NT = c_NT S_NT = S_NT ONE = Cymbol(r'I') ZERO = Cymbol(r'O') # expressions Sig_ = Sig_.T dSig_dEps_ = dSig_dEps_.subs(0, ZERO) * ONE f_ = f_ df_dSig_ = df_dSig_.subs(0, ZERO) * ONE ddf_dEps_ = ddf_dEps_.subs(0, ZERO) * ONE phi_final_ = tr.Property() @tr.cached_property def _get_phi_final_(self): def g_ari_integrity(var1, var2): return 1 - sp.sqrt((1 - var1) * (1 - var2)) omega_NT = g_ari_integrity(omega_N, omega_T) phi_N = (1 - omega_N)**(c_N) * S_N / (r + 1) * (Y_N / S_N)**( r + 1) * H_switch phi_T = (1 - omega_T)**(c_T) * S_T / (r + 1) * (Y_T / S_T)**(r + 1) phi_NT = (1 - omega_NT)**(c_NT) * S_NT / (r + 1) * ( (Y_N + Y_T) / S_NT)**(r + 1) phi_ = f_ + ((1 - eta) * (phi_N + phi_T) + eta * phi_NT) * (bartau / (bartau - m * sig_pi)) return phi_.subs( r, 1 ) # @TODO - fix the passing of the parameter - it damages the T response Phi_final_ = tr.Property() @tr.cached_property def _get_Phi_final_(self): return -self.Sig_signs * self.phi_final_.diff(self.Sig) H_sig_pi_ = H(sig_pi) sig_eff_ = sig_pi / (1 - H(sig_pi) * omega_N) tau_eff_x_ = tau_pi_x / (1 - omega_T) tau_eff_y_ = tau_pi_y / (1 - omega_T) symb_model_params = [ 'E_T', 'gamma_T', 'K_T', 'S_T', 'c_T', 'bartau', 'E_N', 'S_N', 'c_N', 'm', 'f_t', 'f_c', 'f_c0', 'eta', 'r', 'c_NT', 'S_NT' ] # List of expressions for which the methods `get_` symb_expressions = [ ('Sig_', ('w', 's_x', 's_y', 'Sig', 'Eps')), ('dSig_dEps_', ('w', 's_x', 's_y', 'Sig', 'Eps', 'ZERO', 'ONE')), ('f_', ('Eps', 'Sig', 'H_switch')), ('df_dSig_', ('Eps', 'Sig', 'H_switch', 'ZERO', 'ONE')), ('ddf_dEps_', ('Eps', 'Sig', 'H_switch', 'ZERO', 'ONE')), ('phi_final_', ('Eps', 'Sig', 'H_switch')), ('Phi_final_', ('Eps', 'Sig', 'H_switch', 'ZERO', 'ONE')), ('H_sig_pi_', ('Sig', )) ]