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 TFBilinear(TimeFunction): loading_ratio = Float(0.5, TIME=True) time_ratio = Float(0.5, TIME=True) ipw_view = View( Item('t_max'), Item('loading_ratio', editor=FloatRangeEditor(low=0, high=1)), Item('time_ratio', editor=FloatRangeEditor(low=0, high=1)), Item('range_factor')) range_factor = Float(1) def _generate_time_function(self): # Define the monotonic range beyond the unit range # allow for interactive extensions. The fact d_history = np.array([ 0, 1 * self.loading_ratio * self.range_factor, 1 * self.range_factor ]) t_arr = np.array([ 0, self.t_max * self.time_ratio * self.range_factor, self.t_max * self.range_factor ]) return interp1d(t_arr, d_history, bounds_error=False, fill_value=self.t_max)
class TLine(BMCSLeafNode): ''' Time line for the control parameter. This class sets the time-range of the computation - the start and stop time. val is the value of the current time. TODO - the info page including the number of load steps and estimated computation time. TODO - the slide bar is not read-only. How to include a real progress bar? ''' node_name = 'time range' min = bu.Float(0.0, TIME=True) max = bu.Float(1.0, TIME=True) step = bu.Float(0.1, TIME=True) val = bu.Float(0.0) def _val_changed(self): if self.time_change_notifier: self.time_change_notifier(self.val) @on_trait_change('min,max') def _time_range_changed(self): if self.time_range_change_notifier: self.time_range_change_notifier(self.max) time_change_notifier = Callable time_range_change_notifier = Callable ipw_view = View(Item('min', full_size=True), Item('max'), Item('step'), Item('val', style='readonly'))
class BarLayer(ReinfLayer): """"Layer consisting of discrete bar reinforcement""" name = 'Bar layer' ds = Float(16, CS=True) count = Int(1, CS=True) A = tr.Property(Float, depends_on='+CS') """cross section area of reinforcement layers""" @tr.cached_property def _get_A(self): return self.count * np.pi * (self.ds / 2.)**2 P = tr.Property(Float, depends_on='+CS') """permeter of reinforcement layers""" @tr.cached_property def _get_P(self): return self.count * np.pi * (self.ds) def _matmod_default(self): return 'steel' ipw_view = View( Item('matmod', latex=r'\mathrm{behavior}'), Item('z', latex=r'z \mathrm{[mm]}'), Item('ds', latex=r'ds \mathrm{[mm]}'), Item('count', latex='count'), Item('A', latex=r'A [mm^2]'), )
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 TFMonotonic(TimeFunction): ipw_view = View(Item('t_max'), Item('range_factor')) range_factor = Float(10000) def _generate_time_function(self): # Define the monotonic range beyond the unit range # allow for interactive extensions. The fact d_history = np.array([0, 1 * self.range_factor]) t_arr = np.array([0, self.t_max * self.range_factor]) return interp1d(t_arr, d_history)
class TFCyclicSymmetricConstant(TimeFunction): number_of_cycles = Int(10, TIME=True) ipw_view = View(Item('number_of_cycles'), Item('t_max')) def _generate_time_function(self): d_levels = np.zeros((self.number_of_cycles * 2, )) d_levels.reshape(-1, 2)[:, 0] = -1 d_levels.reshape(-1, 2)[:, 1] = 1 d_history = d_levels.flatten() t_arr = np.linspace(0, self.t_max, len(d_history)) return interp1d(t_arr, d_history)
class TFCyclicNonsymmetricIncreasing(TimeFunction): number_of_cycles = Int(10, TIME=True) ipw_view = View(Item('number_of_cycles'), Item('t_max')) def _generate_time_function(self): d_levels = np.linspace(0, 1, self.number_of_cycles * 2) d_levels.reshape(-1, 2)[:, 0] *= 0 d_history = d_levels.flatten() t_arr = np.linspace(0, self.t_max, len(d_history)) return interp1d(t_arr, d_history, bounds_error=False, fill_value=self.t_max)
class CrossSectionShapeBase(InteractiveModel): """"This class describes the geometry of the cross section.""" name = 'Cross Section Shape' H = Float(400, GEO=True) ipw_view = View(Item('H', minmax=(10, 3000), latex='H [mm]'))
class TFCyclicSin(TimeFunction): number_of_cycles = Int(10, TIME=True) phase_shift = Float(0, TIME=True) ipw_view = View(Item('number_of_cycles'), Item('phase_shift'), Item('t_max')) def _generate_time_function(self): t = sp.symbols(r't') p = self.phase_shift tf = sp.sin(2 * self.number_of_cycles * sp.pi * t / self.t_max) if p > 0: T = self.t_max / self.number_of_cycles pT = p * T tf = sp.Piecewise((0, t < pT), (tf.subs(t, t - pT), True)) return sp.lambdify(t, tf, 'numpy')
class FabricLayer(ReinfLayer): """Reinforcement with a grid structure """ name = 'Fabric layer' width = Float(100, CS=True) spacing = Float(14, CS=True) A_roving = Float(1, CS=True) A = tr.Property(Float, depends_on='+CS') """cross section area of reinforcement layers""" @tr.cached_property def _get_A(self): return int(self.width / self.spacing) * self.A_roving P = tr.Property(Float, depends_on='+CS') """cross section area of reinforcement layers""" @tr.cached_property def _get_P(self): raise NotImplementedError def _matmod_default(self): return 'carbon' ipw_view = View( Item('matmod', latex=r'\mathrm{behavior}'), Item('z', latex='z \mathrm{[mm]}'), Item('width', latex='\mathrm{fabric~width} \mathrm{[mm]}'), Item('spacing', latex='\mathrm{rov~spacing} \mathrm{[mm]}'), Item('A_roving', latex='A_r \mathrm{[mm^2]}'), Item('A', latex=r'A [mm^2]', readonly=True), )
class TFCyclicNonsymmetricConstant(TimeFunction): number_of_cycles = Int(10, TIME=True) unloading_ratio = Float(0.5, TIME=True) shift_cycles = Int(0, TIME=True) ipw_view = View( Item('number_of_cycles'), Item('shift_cycles'), Item('unloading_ratio', editor=FloatRangeEditor(low=0, high=1)), Item('t_max')) def _generate_time_function(self): d_1 = np.zeros(1) d_2 = np.zeros(((self.number_of_cycles + self.shift_cycles) * 2, )) d_2.reshape(-1, 2)[self.shift_cycles:, 0] = 1 d_2.reshape(-1, 2)[self.shift_cycles:, 1] = self.unloading_ratio d_history = d_2.flatten() d_arr = np.hstack((d_1, d_history)) t_arr = np.linspace(0, self.t_max, len(d_arr)) return interp1d(t_arr, d_arr, bounds_error=False, fill_value=self.t_max)
class ReinfLayer(InteractiveModel): # TODO: changes in the ipw interactive window doesn't reflect on mkappa # (maybe because these are lists and changing the elements doesn't notify) name = 'Reinf layer' cs_layout = tr.WeakRef z = Float(50, CS=True) """z positions of reinforcement layers""" P = Float(100, CS=True) """Perimeter""" A = Float(100, CS=True) """Cross sectional area""" matmod = EitherType( options=[('steel', SteelReinfMatMod), ('carbon', CarbonReinfMatMod)]) ipw_view = View( Item('matmod', latex=r'\mathrm{behavior}', editor=EitherTypeEditor(show_properties=False)), Item('z', latex='z \mathrm{[mm]}'), Item('A', latex='A \mathrm{[mm^2]}'), ) tree = ['matmod'] def get_N(self, eps): return self.A * self.matmod_.get_sig(eps) def update_plot(self, ax): eps_range = self.matmod_.get_eps_plot_range() N_range = self.get_N(eps_range) ax.plot(eps_range, N_range, color='red') ax.fill_between(eps_range, N_range, 0, color='red', alpha=0.1) ax.set_xlabel(r'$\varepsilon$ [-]') ax.set_ylabel(r'$F$ [N]')
class MATS3DIfcElastic(MATSEval): node_name = "Elastic interface" E_n = Float(1e+6, tooltip='Normal stiffness of the interface [MPa]', MAT=True, unit='MPa/m', symbol='E_\mathrm{n}', desc='Normal stiffness of the interface', auto_set=False, enter_set=True) E_s = Float(1.0, tooltip='Shear stiffness of the interface [MPa]', MAT=True, unit='MPa/m', symbol='E_\mathrm{s}', desc='Shear-modulus of the interface', auto_set=True, enter_set=True) state_var_shapes = {} D_rs = Property(depends_on='E_n,E_s') @cached_property def _get_D_rs(self): return np.array([[self.E_n, 0, 0], [0, self.E_s, 0], [0, 0, self.E_s]], dtype=np.float_) def get_corr_pred(self, u_r, tn1): tau = np.einsum( 'rs,...s->...r', self.D_rs, u_r) grid_shape = tuple([1 for _ in range(len(u_r.shape[:-1]))]) D = self.D_rs.reshape(grid_shape + self.D_rs.shape) return tau, D ipw_view = View( Item('E_s'), Item('E_n') )
class Rectangle(CrossSectionShapeBase): B = Float(200, GEO=True) ipw_view = View( *CrossSectionShapeBase.ipw_view. content, # this will add View Items of the base class CrossSectionShapeBase Item('B', minmax=(10, 500), latex='B \mathrm{[mm]}')) def get_cs_area(self): return self.B * self.H def get_cs_i(self): return self.B * self.H**3 / 12 def get_b(self, z_positions_array): return np.full_like(z_positions_array, self.B) def update_plot(self, ax): ax.fill( [-self.B / 2, self.B / 2, self.B / 2, -self.B / 2, -self.B / 2], [0, 0, self.H, self.H, 0], color='gray', alpha=0.2) ax.plot( [-self.B / 2, self.B / 2, self.B / 2, -self.B / 2, -self.B / 2], [0, 0, self.H, self.H, 0], color='black') ax.autoscale_view() ax.set_aspect('equal') ax.annotate('Area = {} $mm^2$'.format(int(Rectangle.get_cs_area(self)), 0), xy=(-self.H / 2 * 0.8, self.H / 2), color='blue') ax.annotate('I = {} $mm^4$'.format(int(Rectangle.get_cs_i(self)), 0), xy=(-self.H / 2 * 0.8, self.H / 2 * 0.8), color='blue')
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 Circle(CrossSectionShapeBase): # TODO->Rostia: provide input field instead minmax range # H from the base class is used as the D, for the diameter of the circular section H = Float(250, GEO=True) ipw_view = View(Item('H', minmax=(10, 3000), latex='D [mm]'), ) def get_cs_area(self): return np.pi * self.H * self.H def get_cs_i(self): return np.pi * self.H**4 / 4 def get_b(self, z_positions_array): return np.full_like(z_positions_array, self.H) # TODO->Saeed: complete this def update_plot(self, ax): # TODO->Saeed: fix this circle = MPL_Circle((0, self.H / 2), self.H / 2, facecolor=(.5, .5, .5, 0.2), edgecolor=(0, 0, 0, 1)) ax.add_patch(circle) ax.autoscale_view() ax.set_aspect('equal') ax.annotate('Area = {} $mm^2$'.format(int(self.get_cs_area()), 0), xy=(-self.H / 2 * 0.8, self.H / 2), color='blue') ax.annotate('I = {} $mm^4$'.format(int(self.get_cs_i()), 0), xy=(-self.H / 2 * 0.8, self.H / 2 * 0.8), color='blue')
class MATS2DMplCSDEEQ(MATSBase, InteractiveModel): concrete_type = tr.Int gamma_T = tr.Float(100000., label="Gamma", desc=" Tangential Kinematic hardening modulus", enter_set=True, auto_set=False) K_T = tr.Float(10000., label="K", desc="Tangential Isotropic harening", enter_set=True, auto_set=False) S_T = tr.Float(0.005, label="S", desc="Damage strength", enter_set=True, auto_set=False) r_T = tr.Float(9., label="r", desc="Damage cumulation parameter", enter_set=True, auto_set=False) e_T = tr.Float(12., label="e", desc="Damage cumulation parameter", enter_set=True, auto_set=False) c_T = tr.Float(4.6, label="c", desc="Damage cumulation parameter", enter_set=True, auto_set=False) tau_pi_bar = tr.Float(1.7, label="Tau_bar", desc="Reversibility limit", enter_set=True, auto_set=False) a = tr.Float(0.003, label="a", desc="Lateral pressure coefficient", enter_set=True, auto_set=False) ipw_view = View( Item('gamma_T', latex=r'\gamma_\mathrm{T}', minmax=(10,100000)), Item('K_T', latex=r'K_\mathrm{T}', minmax=(10, 10000)), Item('S_T', latex=r'S_\mathrm{T}', minmax=(0.001, 0.01)), Item('r_T', latex=r'r_\mathrm{T}', minmax=(1, 3)), Item('e_T', latex=r'e_\mathrm{T}', minmax=(1, 40)), Item('c_T', latex=r'c_\mathrm{T}', minmax=(1, 10)), Item('tau_pi_bar', latex=r'\bar{\tau}', minmax=(1, 10)), Item('a', latex=r'a', minmax=(0.001, 3)), ) # ------------------------------------------- # Normal_Tension constitutive law parameters (without cumulative normal strain) # ------------------------------------------- Ad = tr.Float(100.0, label="a", desc="brittleness coefficient", enter_set=True, auto_set=False) eps_0 = tr.Float(0.00008, label="a", desc="threshold strain", enter_set=True, auto_set=False) # ----------------------------------------------- # Normal_Compression constitutive law parameters # ----------------------------------------------- K_N = tr.Float(10000., label="K_N", desc=" Normal isotropic harening", enter_set=True, auto_set=False) gamma_N = tr.Float(5000., label="gamma_N", desc="Normal kinematic hardening", enter_set=True, auto_set=False) sigma_0 = tr.Float(30., label="sigma_0", desc="Yielding stress", enter_set=True, auto_set=False) # ------------------------------------------------------------------------- # Cached elasticity tensors # ------------------------------------------------------------------------- E = tr.Float(35e+3, label="E", desc="Young's Modulus", auto_set=False, input=True) nu = tr.Float(0.2, label='nu', desc="Poison ratio", auto_set=False, input=True) def _get_lame_params(self): # la = self.E * self.nu / ((1. + self.nu) * (1. - 2. * self.nu)) # # second Lame parameter (shear modulus) # mu = self.E / (2. + 2. * self.nu) la = self.E * self.nu / ((1. + self.nu) * (1. - self.nu)) mu = self.E / (2. + 2. * self.nu) return la, mu D_abef = tr.Property(tr.Array, depends_on='+input') @tr.cached_property def _get_D_abef(self): la = self._get_lame_params()[0] mu = self._get_lame_params()[1] delta = np.identity(2) D_abef = (np.einsum(',ij,kl->ijkl', la, delta, delta) + np.einsum(',ik,jl->ijkl', mu, delta, delta) + np.einsum(',il,jk->ijkl', mu, delta, delta)) return D_abef @tr.cached_property def _get_state_var_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, 2), 'eps_T_pi_Emna': (self.n_mp, 2), } #-------------------------------------------------------------- # microplane constitutive law (normal behavior CP + TD) # (without cumulative normal strain for fatigue under tension) #-------------------------------------------------------------- def get_normal_law(self, eps_N_Emn, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, eps_aux): eps_N_Aux = self._get_e_N_Emn_2(eps_aux) E_N = self.E / (1.0 - 2.0 * self.nu) # E_N = self.E * (1.0 + 2.0 * self.nu) / (1.0 - self.nu**2) # When deciding if a microplane is in tensile or compression, we define a strain boundary such that that # sigmaN <= 0 if eps_N < 0, avoiding entering in the quadrant of compressive strains and traction sigma_trial = E_N * (eps_N_Emn - eps_N_p_Emn) pos1 = [(eps_N_Emn < -1e-6) & (sigma_trial > 1e-6)] # looking for microplanes violating strain boundary sigma_trial [pos1[0]] = 0 pos = eps_N_Emn > 1e-6 # microplanes under traction pos2 = eps_N_Emn < -1e-6 # microplanes under compression H = 1.0 * pos H2 = 1.0 * pos2 # thermo forces sigma_N_Emn_tilde = E_N * (eps_N_Emn - eps_N_p_Emn) sigma_N_Emn_tilde[pos1[0]] = 0 # imposing strain boundary Z = self.K_N * z_N_Emn X = self.gamma_N * alpha_N_Emn * H2 h = (self.sigma_0 + Z) * H2 f_trial = (abs(sigma_N_Emn_tilde - X) - h) * H2 # threshold plasticity thres_1 = f_trial > 1e-6 delta_lamda = f_trial / \ (E_N / (1 - omega_N_Emn) + abs(self.K_N) + self.gamma_N) * thres_1 eps_N_p_Emn = eps_N_p_Emn + delta_lamda * \ np.sign(sigma_N_Emn_tilde - X) z_N_Emn = z_N_Emn + delta_lamda alpha_N_Emn = alpha_N_Emn + delta_lamda * \ np.sign(sigma_N_Emn_tilde - X) def R_N(r_N_Emn): return (1.0 / self.Ad) * (-r_N_Emn / (1.0 + r_N_Emn)) Y_N = 0.5 * H * E_N * (eps_N_Emn - eps_N_p_Emn) ** 2.0 Y_0 = 0.5 * E_N * self.eps_0 ** 2.0 f = (Y_N - (Y_0 + R_N(r_N_Emn))) # threshold damage thres_2 = f > 1e-6 def f_w(Y): return 1.0 - 1.0 / (1.0 + self.Ad * (Y - Y_0)) omega_N_Emn[f > 1e-6] = f_w(Y_N)[f > 1e-6] r_N_Emn[f > 1e-6] = -omega_N_Emn[f > 1e-6] sigma_N_Emn = (1.0 - H * omega_N_Emn) * E_N * (eps_N_Emn - eps_N_p_Emn) pos1 = [(eps_N_Emn < -1e-6) & (sigma_trial > 1e-6)] # looking for microplanes violating strain boundary sigma_N_Emn[pos1[0]] = 0 Z = self.K_N * z_N_Emn X = self.gamma_N * alpha_N_Emn * H2 return omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, Z, X, Y_N #------------------------------------------------------------------------- # microplane constitutive law (Tangential CSD)-(Pressure sensitive cumulative damage) #------------------------------------------------------------------------- def get_tangential_law(self, eps_T_Emna, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn): E_T = self.E / (1.0 + self.nu) # E_T = self.E * (1.0 - 4 * self.nu) / \ # ((1.0 + self.nu) * (1.0 - 2 * self.nu)) # E_T = self.E * (1.0 - 3.0 * self.nu) / (1.0 - self.nu**2) # thermo forces 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 = np.sqrt( np.einsum( '...na,...na->...n', (sig_pi_trial - X), (sig_pi_trial - X)) ) Y = 0.5 * E_T * \ np.einsum( '...na,...na->...n', (eps_T_Emna - eps_T_pi_Emna), (eps_T_Emna - eps_T_pi_Emna)) # threshold f = norm_1 - self.tau_pi_bar - \ Z + self.a * sigma_N_Emn plas_1 = f > 1e-6 elas_1 = f < 1e-6 delta_lamda = f / \ (E_T / (1.0 - omega_T_Emn) + self.gamma_T + self.K_T) * plas_1 norm_2 = 1.0 * elas_1 + np.sqrt( np.einsum( '...na,...na->...n', (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 - omega_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 - omega_T_Emn)) / norm_2 omega_T_Emn += ((1 - omega_T_Emn) ** self.c_T) * \ (delta_lamda * (Y / self.S_T) ** self.r_T) * \ (self.tau_pi_bar / (self.tau_pi_bar + self.a * sigma_N_Emn)) ** self.e_T 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 z_T_Emn = z_T_Emn + delta_lamda sigma_T_Emna = np.einsum( '...n,...na->...na', (1 - omega_T_Emn), E_T * (eps_T_Emna - eps_T_pi_Emna)) Z = self.K_T * z_T_Emn X = self.gamma_T * alpha_T_Emna Y = 0.5 * E_T * \ np.einsum( '...na,...na->...n', (eps_T_Emna - eps_T_pi_Emna), (eps_T_Emna - eps_T_pi_Emna)) return omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, Z, X, Y # #------------------------------------------------------------------------- # # MICROPLANE-Kinematic constraints # #------------------------------------------------------------------------- #------------------------------------------------- # get the operator of the microplane normals _MPNN = tr.Property(depends_on='n_mp') @tr.cached_property def _get__MPNN(self): print(self._MPN) MPNN_nij = np.einsum('ni,nj->nij', self._MPN, self._MPN) return MPNN_nij # get the third order tangential tensor (operator) for each microplane _MPTT = tr.Property(depends_on='n_mp') @tr.cached_property def _get__MPTT(self): delta = np.identity(2) 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 def _get_e_N_Emn_2(self, eps_Emab): # get the normal strain array for each microplane return np.einsum('nij,...ij->...n', self._MPNN, eps_Emab) def _get_e_T_Emnar_2(self, eps_Emab): # get the tangential strain vector array for each microplane MPTT_ijr = self._get__MPTT() return np.einsum('nija,...ij->...na', MPTT_ijr, eps_Emab) #-------------------------------------------------------- # return the state variables (Damage , inelastic strains) #-------------------------------------------------------- def _get_state_variables(self, eps_Emab, int_var, eps_aux): e_N_arr = self._get_e_N_Emn_2(eps_Emab) e_T_vct_arr = self._get_e_T_Emnar_2(eps_Emab) omega_N_Emn = int_var[:, 0] z_N_Emn = int_var[:, 1] alpha_N_Emn = int_var[:, 2] r_N_Emn = int_var[:, 3] eps_N_p_Emn = int_var[:, 4] sigma_N_Emn = int_var[:, 5] omega_T_Emn = int_var[:, 9] z_T_Emn = int_var[:, 10] alpha_T_Emna = int_var[:, 11:13] eps_T_pi_Emna = int_var[:, 13:15] omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, Z_n, X_n, Y_n = self.get_normal_law(e_N_arr, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, eps_aux) omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, Z_T, X_T, Y_T = self.get_tangential_law(e_T_vct_arr, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) # Definition internal variables / forces per column: 1) damage N, 2)iso N, 3)kin N, 4)consolidation N, 5) eps p N, # 6) sigma N, 7) iso F N, 8) kin F N, 9) energy release N, 10) damage T, 11) iso T, 12-13) kin T, 14-15) eps p T, # 16-17) sigma T, 18) iso F T, 19-20) kin F T, 21) energy release T int_var[:, 0] = omega_N_Emn int_var[:, 1] = z_N_Emn int_var[:, 2] = alpha_N_Emn int_var[:, 3] = r_N_Emn int_var[:, 4] = eps_N_p_Emn int_var[:, 5] = sigma_N_Emn int_var[:, 6] = Z_n int_var[:, 7] = X_n int_var[:, 8] = Y_n int_var[:, 9] = omega_T_Emn int_var[:, 10] = z_T_Emn int_var[:, 11:13] = alpha_T_Emna int_var[:, 13:15] = eps_T_pi_Emna int_var[:, 15:17] = sigma_T_Emna int_var[:, 17] = Z_T int_var[:, 18:20] = X_T int_var[:, 20] = Y_T return int_var #--------------------------------------------------------------------- # Extra homogenization of damage tensor in case of two damage parameters # Returns the 4th order damage tensor 'beta4' using (ref. [Baz99], Eq.(63)) #--------------------------------------------------------------------- def _get_beta_Emabcd_2(self, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, eps_aux): # Returns the 4th order damage tensor 'beta4' using #(cf. [Baz99], Eq.(63)) eps_N_Emn = self._get_e_N_Emn_2(eps_Emab) eps_T_Emna = self._get_e_T_Emnar_2(eps_Emab) omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, Z_n, X_n, Y_n = self.get_normal_law( eps_N_Emn, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, eps_aux) omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, Z_T, X_T, Y_T = self.get_tangential_law( eps_T_Emna, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) delta = np.identity(2) beta_N = np.sqrt(1. - omega_N_Emn) beta_T = np.sqrt(1. - omega_T_Emn) beta_ijkl = np.einsum('n, ...n,ni, nj, nk, nl -> ...ijkl', self._MPW, beta_N, self._MPN, self._MPN, self._MPN, self._MPN) + \ 0.25 * (np.einsum('n, ...n,ni, nk, jl -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) + np.einsum('n, ...n,ni, nl, jk -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) + np.einsum('n, ...n,nj, nk, il -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) + np.einsum('n, ...n,nj, nl, ik -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) - 4.0 * np.einsum('n, ...n, ni, nj, nk, nl -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, self._MPN, self._MPN)) return beta_ijkl #----------------------------------------------------------- # Integration of the (inelastic) strains for each microplane #----------------------------------------------------------- def _get_eps_p_Emab(self, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn, eps_aux): eps_N_Emn = self._get_e_N_Emn_2(eps_Emab) eps_T_Emna = self._get_e_T_Emnar_2(eps_Emab) # plastic normal strains omegaN, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, Z_n, X_n, Y_n = self.get_normal_law( eps_N_Emn, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, eps_aux) # sliding tangential strains omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, Z_T, X_T, Y_T = self.get_tangential_law( eps_T_Emna, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) delta = np.identity(2) # 2-nd order plastic (inelastic) tensor eps_p_Emab = ( np.einsum('n,...n,na,nb->...ab', self._MPW, eps_N_p_Emn, self._MPN, self._MPN) + 0.5 * ( np.einsum('n,...nf,na,fb->...ab', self._MPW, eps_T_pi_Emna, self._MPN, delta) + np.einsum('n,...nf,nb,fa->...ab', 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, t_n1, int_var, eps_aux, F): # Definition internal variables / forces per column: 1) damage N, 2)iso N, 3)kin N, 4)consolidation N, 5) eps p N, # 6) sigma N, 7) iso F N, 8) kin F N, 9) energy release N, 10) damage T, 11) iso T, 12-13) kin T, 14-15) eps p T, # 16-17) sigma T, 18) iso F T, 19-20) kin F T, 21) energy release T # Corrector predictor computation. #------------------------------------------------------------------ # Damage tensor (4th order) using product- or sum-type symmetrization: #------------------------------------------------------------------ eps_N_Emn = self._get_e_N_Emn_2(eps_Emab) eps_T_Emna = self._get_e_T_Emnar_2(eps_Emab) omega_N_Emn = int_var[:, 0] z_N_Emn = int_var[:, 1] alpha_N_Emn = int_var[:, 2] r_N_Emn = int_var[:, 3] eps_N_p_Emn = int_var[:, 4] sigma_N_Emn = int_var[:, 5] omega_T_Emn = int_var[:, 9] z_T_Emn = int_var[:, 10] alpha_T_Emna = int_var[:, 11:13] eps_T_pi_Emna = int_var[:, 13:15] beta_Emabcd = self._get_beta_Emabcd_2( eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, eps_aux ) #------------------------------------------------------------------ # Damaged stiffness tensor calculated based on the damage tensor beta4: #------------------------------------------------------------------ D_Emabcd = np.einsum( '...ijab, abef, ...cdef->...ijcd', 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, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn, eps_aux) # elastic strain tensor eps_e_Emab = eps_Emab - eps_p_Emab # calculation of the stress tensor sig_Emab = np.einsum('...abcd,...cd->...ab', D_Emabcd, eps_e_Emab) return D_Emabcd, sig_Emab, eps_p_Emab #----------------------------------------------- # number of microplanes #----------------------------------------------- n_mp = tr.Constant(360) #----------------------------------------------- # get the normal vectors of the microplanes #----------------------------------------------- _MPN = tr.Property(depends_on='n_mp') @tr.cached_property def _get__MPN(self): # microplane normals: alpha_list = np.linspace(0, 2 * np.pi, self.n_mp) MPN = np.array([[np.cos(alpha), np.sin(alpha)] for alpha in alpha_list]) return MPN #------------------------------------- # get the weights of the microplanes #------------------------------------- _MPW = tr.Property(depends_on='n_mp') @tr.cached_property def _get__MPW(self): MPW = np.ones(self.n_mp) / self.n_mp * 2 return MPW
class MS1(MATS3DEval, InteractiveModel): gamma_T = tr.Float(100000., label="gamma_T", desc=" Tangential Kinematic hardening modulus", enter_set=True, auto_set=False) K_T = tr.Float(10000., label="K_T", desc="Tangential Isotropic harening", enter_set=True, auto_set=False) S_T = tr.Float(0.005, label="S_T", desc="Damage strength", enter_set=True, auto_set=False) r_T = tr.Float(9., label="r", desc="Damage cumulation parameter", enter_set=True, auto_set=False) p_T = tr.Float(12., label="p_T", desc="Damage cumulation parameter", enter_set=True, auto_set=False) c_T = tr.Float(4.6, label="c_T", desc="Damage cumulation parameter", enter_set=True, auto_set=False) sigma_T_0 = tr.Float(1.7, label="sigma_T_0", desc="Reversibility limit", enter_set=True, auto_set=False) m_T = tr.Float(0.003, label="m_T", desc="Lateral pressure coefficient", enter_set=True, auto_set=False) # ------------------------------------------- # Normal_Tension constitutive law parameters (without cumulative normal strain) # ------------------------------------------- Ad = tr.Float(100.0, label="A_d", desc="brittleness coefficient", enter_set=True, auto_set=False) eps_0 = tr.Float(0.00008, label="eps_N_0", desc="threshold strain", enter_set=True, auto_set=False) # ----------------------------------------------- # Normal_Compression constitutive law parameters # ----------------------------------------------- K_N = tr.Float(10000., label="K_N", desc=" Normal isotropic harening", enter_set=True, auto_set=False) gamma_N = tr.Float(5000., label="gamma_N", desc="Normal kinematic hardening", enter_set=True, auto_set=False) sigma_N_0 = tr.Float(30., label="sigma_N_0", desc="Yielding stress", enter_set=True, auto_set=False) # ------------------------------------------------------------------------- # Cached elasticity tensors # ------------------------------------------------------------------------- E = tr.Float(35e+3, label="E", desc="Young's Modulus", auto_set=False, input=True) nu = tr.Float(0.2, label='nu', desc="Poison ratio", auto_set=False, input=True) ipw_view = View( Item('gamma_T', latex=r'\gamma_\mathrm{T}', minmax=(10, 100000)), Item('K_T', latex=r'K_\mathrm{T}', minmax=(10, 10000)), Item('S_T', latex=r'S_\mathrm{T}', minmax=(0.001, 0.01)), Item('r_T', latex=r'r_\mathrm{T}', minmax=(1, 3)), Item('p_T', latex=r'e_\mathrm{T}', minmax=(1, 40)), Item('c_T', latex=r'c_\mathrm{T}', minmax=(1, 10)), Item('sigma_T_0', latex=r'\bar{sigma}^\pi_{T}', minmax=(1, 10)), Item('m_T', latex=r'm_\mathrm{T}', minmax=(0.001, 3)), ) n_D = 3 state_var_shapes = tr.Property @tr.cached_property def _get_state_var_shapes(self): return { name: (self.n_mp, ) + shape for name, shape in self.mic_state_var_shapes.items() } mic_state_var_shapes = dict( omega_N_Emn=(), # damage N z_N_Emn=(), alpha_N_Emn=(), r_N_Emn=(), eps_N_p_Emn=(), sigma_N_Emn=(), omega_T_Emn=(), z_T_Emn=(), alpha_T_Emna=(n_D, ), eps_T_pi_Emna=(n_D, ), ) ''' State variables 1) damage N, 2) iso N, 3) kin N, 4) consolidation N, 5) eps p N, 6) sigma N, 7) iso F N, 8) kin F N, 9) energy release N, 10) damage T, 11) iso T, 12-13) kin T, 14-15) eps p T, 16-17) sigma T, 18) iso F T, 19-20) kin F T, 21) energy release T ''' # -------------------------------------------------------------- # microplane constitutive law (normal behavior CP + TD) # (without cumulative normal strain for fatigue under tension) # -------------------------------------------------------------- def get_normal_law(self, eps_N_Emn, omega_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) # When deciding if a microplane is in tensile or compression, # we define a strain boundary such that # sigmaN <= 0 if eps_N < 0, avoiding entering in the quadrant # of compressive strains and traction sigma_trial = E_N * (eps_N_Emn - eps_N_p_Emn) # looking for microplanes violating strain boundary pos1 = [(eps_N_Emn < -1e-6) & (sigma_trial > 1e-6)] sigma_trial[pos1[0]] = 0 pos = eps_N_Emn > 1e-6 # microplanes under traction pos2 = eps_N_Emn < -1e-6 # microplanes under compression H = 1.0 * pos H2 = 1.0 * pos2 # thermo forces sigma_N_Emn_tilde = E_N * (eps_N_Emn - eps_N_p_Emn) sigma_N_Emn_tilde[pos1[0]] = 0 # imposing strain boundary Z = self.K_N * z_N_Emn X = self.gamma_N * alpha_N_Emn * H2 h = (self.sigma_N_0 + Z) * H2 f_trial = (abs(sigma_N_Emn_tilde - X) - h) * H2 # threshold plasticity thres_1 = f_trial > 1e-6 delta_lamda = f_trial / \ (E_N / (1 - omega_N_Emn) + abs(self.K_N) + self.gamma_N) * thres_1 eps_N_p_Emn += delta_lamda * \ np.sign(sigma_N_Emn_tilde - X) z_N_Emn += delta_lamda alpha_N_Emn += delta_lamda * \ np.sign(sigma_N_Emn_tilde - X) def R_N(r_N_Emn): return (1.0 / self.Ad) * (-r_N_Emn / (1.0 + r_N_Emn)) Y_N = 0.5 * H * E_N * (eps_N_Emn - eps_N_p_Emn)**2.0 Y_0 = 0.5 * E_N * self.eps_0**2.0 f = (Y_N - (Y_0 + R_N(r_N_Emn))) # threshold damage thres_2 = f > 1e-6 def f_w(Y): return 1.0 - 1.0 / (1.0 + self.Ad * (Y - Y_0)) omega_N_Emn[f > 1e-6] = f_w(Y_N)[f > 1e-6] omega_N_Emn[...] = np.clip(omega_N_Emn, 0, 0.9999) r_N_Emn[f > 1e-6] = -omega_N_Emn[f > 1e-6] sigma_N_Emn = (1.0 - H * omega_N_Emn) * E_N * (eps_N_Emn - eps_N_p_Emn) pos1 = [(eps_N_Emn < -1e-6) & (sigma_trial > 1e-6) ] # looking for microplanes violating strain boundary sigma_N_Emn[pos1[0]] = 0 Z = self.K_N * z_N_Emn X = self.gamma_N * alpha_N_Emn * H2 return sigma_N_Emn, Z, X, Y_N # ------------------------------------------------------------------------- # microplane constitutive law (Tangential CSD)-(Pressure sensitive cumulative damage) # ------------------------------------------------------------------------- def get_tangential_law(self, eps_T_Emna, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn): E_T = self.E * (1.0 - 4 * self.nu) / \ ((1.0 + self.nu) * (1.0 - 2 * self.nu)) # thermo forces 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 = np.sqrt( np.einsum('...na,...na->...n', (sig_pi_trial - X), (sig_pi_trial - X))) Y = 0.5 * E_T * \ np.einsum( '...na,...na->...n', (eps_T_Emna - eps_T_pi_Emna), (eps_T_Emna - eps_T_pi_Emna)) # threshold f = norm_1 - self.sigma_T_0 - Z + self.m_T * sigma_N_Emn plas_1 = f > 1e-6 elas_1 = f < 1e-6 delta_lamda = f / \ (E_T / (1.0 - omega_T_Emn) + self.gamma_T + self.K_T) * plas_1 norm_2 = 1.0 * elas_1 + np.sqrt( np.einsum('...na,...na->...n', (sig_pi_trial - X), (sig_pi_trial - X))) * plas_1 eps_T_pi_Emna[..., 0] += plas_1 * delta_lamda * \ ((sig_pi_trial[..., 0] - X[..., 0]) / (1.0 - omega_T_Emn)) / norm_2 eps_T_pi_Emna[..., 1] += plas_1 * delta_lamda * \ ((sig_pi_trial[..., 1] - X[..., 1]) / (1.0 - omega_T_Emn)) / norm_2 eps_T_pi_Emna[..., 2] += plas_1 * delta_lamda * \ ((sig_pi_trial[..., 2] - X[..., 2]) / (1.0 - omega_T_Emn)) / norm_2 omega_T_Emn += ((1 - omega_T_Emn) ** self.c_T) * \ (delta_lamda * (Y / self.S_T) ** self.r_T) * \ (self.sigma_T_0 / (self.sigma_T_0 + self.m_T * sigma_N_Emn)) ** self.p_T omega_T_Emn[...] = np.clip(omega_T_Emn, 0, 0.9999) alpha_T_Emna[..., 0] += plas_1 * delta_lamda * \ (sig_pi_trial[..., 0] - X[..., 0]) / norm_2 alpha_T_Emna[..., 1] += plas_1 * delta_lamda * \ (sig_pi_trial[..., 1] - X[..., 1]) / norm_2 alpha_T_Emna[..., 2] += plas_1 * delta_lamda * \ (sig_pi_trial[..., 2] - X[..., 2]) / norm_2 z_T_Emn += delta_lamda sigma_T_Emna = np.einsum('...n,...na->...na', (1 - omega_T_Emn), E_T * (eps_T_Emna - eps_T_pi_Emna)) Z = self.K_T * z_T_Emn X = self.gamma_T * alpha_T_Emna Y = 0.5 * E_T * \ np.einsum( '...na,...na->...n', (eps_T_Emna - eps_T_pi_Emna), (eps_T_Emna - eps_T_pi_Emna)) return sigma_T_Emna, Z, X, Y # #------------------------------------------------------------------------- # # MICROPLANE-Kinematic constraints # #------------------------------------------------------------------------- # ------------------------------------------------- # get the operator of the microplane normals _MPNN = tr.Property(depends_on='n_mp') @tr.cached_property def _get__MPNN(self): MPNN_nij = np.einsum('ni,nj->nij', self._MPN, self._MPN) return MPNN_nij # get the third order tangential tensor (operator) for each microplane _MPTT = tr.Property(depends_on='n_mp') @tr.cached_property def _get__MPTT(self): delta = self.DELTA 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 def _get_e_N_Emn_2(self, eps_Emab): # get the normal strain array for each microplane return np.einsum('nij,...ij->...n', self._MPNN, eps_Emab) def _get_e_T_Emnar_2(self, eps_Emab): # get the tangential strain vector array for each microplane MPTT_ijr = self._get__MPTT() return np.einsum('nija,...ij->...na', MPTT_ijr, eps_Emab) # --------------------------------------------------------------------- # Extra homogenization of damage tensor in case of two damage parameters # Returns the 4th order damage tensor 'beta4' using (ref. [Baz99], Eq.(63)) # --------------------------------------------------------------------- def _get_beta_Emabcd_2(self, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna): # Returns the 4th order damage tensor 'beta4' using # (cf. [Baz99], Eq.(63)) eps_N_Emn = self._get_e_N_Emn_2(eps_Emab) eps_T_Emna = self._get_e_T_Emnar_2(eps_Emab) sigma_N_Emn, Z_n, X_n, Y_n = self.get_normal_law( eps_N_Emn, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn) sigma_T_Emna, Z_T, X_T, Y_T = self.get_tangential_law( eps_T_Emna, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) delta = self.DELTA beta_N = np.sqrt(1. - omega_N_Emn) beta_T = np.sqrt(1. - omega_T_Emn) beta_ijkl = np.einsum('n, ...n,ni, nj, nk, nl -> ...ijkl', self._MPW, beta_N, self._MPN, self._MPN, self._MPN, self._MPN) + \ 0.25 * (np.einsum('n, ...n,ni, nk, jl -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) + np.einsum('n, ...n,ni, nl, jk -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) + np.einsum('n, ...n,nj, nk, il -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) + np.einsum('n, ...n,nj, nl, ik -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) - 4.0 * np.einsum('n, ...n, ni, nj, nk, nl -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, self._MPN, self._MPN)) return beta_ijkl DELTA = np.identity(n_D) # ----------------------------------------------------------- # Integration of the (inelastic) strains for each microplane # ----------------------------------------------------------- def _get_eps_p_Emab(self, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, omega_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_Emnar_2(eps_Emab) # plastic normal strains sigma_N_Emn, Z_n, X_n, Y_n = self.get_normal_law( eps_N_Emn, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn) # sliding tangential strains sigma_T_Emna, Z_T, X_T, Y_T = self.get_tangential_law( eps_T_Emna, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_N_Emn) # 2-nd order plastic (inelastic) tensor eps_p_Emab = (np.einsum('n,...n,na,nb->...ab', self._MPW, eps_N_p_Emn, self._MPN, self._MPN) + 0.5 * (np.einsum('n,...nf,na,fb->...ab', self._MPW, eps_T_pi_Emna, self._MPN, self.DELTA) + np.einsum('n,...nf,nb,fa->...ab', self._MPW, eps_T_pi_Emna, self._MPN, self.DELTA))) return eps_p_Emab def get_corr_pred(self, eps_Emab, t_n1, **Eps_k): """Evaluation - get the corrector and predictor """ # Corrector predictor computation. # ------------------------------------------------------------------ # Damage tensor (4th order) using product- or sum-type symmetrization: # ------------------------------------------------------------------ beta_Emabcd = self._get_beta_Emabcd_2(eps_Emab, **Eps_k) # ------------------------------------------------------------------ # Damaged stiffness tensor calculated based on the damage tensor beta4: # ------------------------------------------------------------------ D_Emabcd = np.einsum('...ijab, abef, ...cdef->...ijcd', 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, **Eps_k) # elastic strain tensor eps_e_Emab = eps_Emab - eps_p_Emab # calculation of the stress tensor sig_Emab = np.einsum('...abcd,...cd->...ab', D_Emabcd, eps_e_Emab) return sig_Emab, D_Emabcd
class MKappa(InteractiveModel, InjectSymbExpr): """Class returning the moment curvature relationship.""" name = 'Moment-Curvature' symb_class = MKappaSymbolic cs_design = Instance(CrossSectionDesign, ()) tree = ['cs_design'] # Use PrototypedFrom only when the prototyped object is a class # (The prototyped attribute behaves similarly # to a delegated attribute, until it is explicitly # changed; from that point forward, the prototyped attribute # changes independently from its prototype.) # (it's kind of like tr.DelegatesTo('cs_design.cross_section_shape')) cross_section_shape = tr.DelegatesTo('cs_design') cross_section_shape_ = tr.DelegatesTo('cs_design') cross_section_layout = tr.DelegatesTo('cs_design') matrix_ = tr.DelegatesTo('cs_design') # Geometry H = tr.DelegatesTo('cross_section_shape_') DEPSTR = 'state_changed' n_m = Int( 100, DSC=True, desc= 'Number of discretization points along the height of the cross-section' ) # @todo: fix the dependency - `H` should be replaced by _GEO z_m = tr.Property(depends_on=DEPSTR) @tr.cached_property def _get_z_m(self): return np.linspace(0, self.H, self.n_m) low_kappa = Float(0.0, BC=True, GEO=True) high_kappa = Float(0.00002, BC=True, GEO=True) n_kappa = Int(100, BC=True) step_kappa = tr.Property(Float, depends_on='low_kappa, high_kappa') @tr.cached_property def _get_step_kappa(self): return float((self.high_kappa - self.low_kappa) / self.n_kappa) kappa_slider = Float(0.0000001) ipw_view = View( Item( 'low_kappa', latex=r'\text{Low}~\kappa'), #, editor=FloatEditor(step=0.00001)), Item('high_kappa', latex=r'\text{High}~\kappa' ), # , editor=FloatEditor(step=0.00001)), Item('n_kappa', latex='n_{\kappa}'), Item('plot_strain'), Item('solve_for_eps_bot_pointwise'), Item('n_m', latex='n_m'), Item('kappa_slider', latex='\kappa', readonly=True), # editor=FloatRangeEditor(low_name='low_kappa', # high_name='high_kappa', # n_steps_name='n_kappa') # ), time_editor=HistoryEditor( var='kappa_slider', min_var='low_kappa', max_var='high_kappa', ), ) idx = tr.Property(depends_on='kappa_slider') apply_material_safety_factors = tr.Bool(False) @tr.cached_property def _get_idx(self): ks = self.kappa_slider idx = np.argmax(ks <= self.kappa_t) return idx kappa_t = tr.Property(tr.Array(np.float_), depends_on=DEPSTR) '''Curvature values for which the bending moment must be found ''' @tr.cached_property def _get_kappa_t(self): return np.linspace(self.low_kappa, self.high_kappa, self.n_kappa) z_j = tr.Property def _get_z_j(self): return self.cross_section_layout.z_j A_j = tr.Property def _get_A_j(self): return self.cross_section_layout.A_j # Normal force in steel (tension and compression) def get_N_s_tj(self, kappa_t, eps_bot_t): # get the strain at the height of the reinforcement eps_z_tj = self.symb.get_eps_z(kappa_t[:, np.newaxis], eps_bot_t[:, np.newaxis], self.z_j[np.newaxis, :]) # Get the crack bridging force in each reinforcement layer # given the corresponding crack-bridge law. N_s_tj = self.cross_section_layout.get_N_tj(eps_z_tj) return N_s_tj # TODO - [RC] avoid repeated evaluations of stress profile in # N and M calculations for the same inputs as it # is the case now. def get_sig_c_z(self, kappa_t, eps_bot_t, z_tm): """Get the stress profile over the height""" eps_z = self.symb.get_eps_z(kappa_t[:, np.newaxis], eps_bot_t[:, np.newaxis], z_tm) sig_c_z = self.matrix_.get_sig(eps_z) return sig_c_z # Normal force in concrete (tension and compression) def get_N_c_t(self, kappa_t, eps_bot_t): z_tm = self.z_m[np.newaxis, :] b_z_m = self.cross_section_shape_.get_b(z_tm) N_z_tm2 = b_z_m * self.get_sig_c_z(kappa_t, eps_bot_t, z_tm) return np.trapz(N_z_tm2, x=z_tm, axis=-1) def get_N_t(self, kappa_t, eps_bot_t): N_s_t = np.sum(self.get_N_s_tj(kappa_t, eps_bot_t), axis=-1) N_c_t = self.get_N_c_t(kappa_t, eps_bot_t) return N_c_t + N_s_t # SOLVER: Get eps_bot to render zero force # num_of_trials = tr.Int(30) eps_bot_t = tr.Property(depends_on=DEPSTR) r'''Resolve the tensile strain to get zero normal force for the prescribed curvature''' # @tr.cached_property # def _get_eps_bot_t(self): # initial_step = (self.high_kappa - self.low_kappa) / self.num_of_trials # for i in range(self.num_of_trials): # print('Solution started...') # res = root(lambda eps_bot_t: self.get_N_t(self.kappa_t, eps_bot_t), # 0.0000001 + np.zeros_like(self.kappa_t), tol=1e-6) # if res.success: # print('success high_kappa: ', self.high_kappa) # if i == 0: # print('Note: high_kappa success from 1st try! selecting a higher value for high_kappa may produce ' # 'a more reliable result!') # return res.x # else: # print('failed high_kappa: ', self.high_kappa) # self.high_kappa -= initial_step # self.kappa_t = np.linspace(self.low_kappa, self.high_kappa, self.n_kappa) # # print('No solution', res.message) # return res.x # @tr.cached_property # def _get_eps_bot_t(self): # res = root(lambda eps_bot_t: self.get_N_t(self.kappa_t, eps_bot_t), # 0.0000001 + np.zeros_like(self.kappa_t), tol=1e-6) # if not res.success: # raise SolutionNotFoundError('No solution', res.message) # return res.x solve_for_eps_bot_pointwise = Bool(True, BC=True, GEO=True) @tr.cached_property def _get_eps_bot_t(self): if self.solve_for_eps_bot_pointwise: """ INFO: Instability in eps_bot solutions was caused by unsuitable init_guess value causing a convergence to non-desired solutions. Solving the whole kappa_t array improved the init_guess after each calculated value, however, instability still there. The best results were obtained by taking the last solution as the init_guess for the next solution like in the following.. """ # One by one solution for kappa values eps_bot_sol_for_pos_kappa = self._get_eps_bot_piecewise_sol( kappa_pos=True) eps_bot_sol_for_neg_kappa = self._get_eps_bot_piecewise_sol( kappa_pos=False) res = np.concatenate( [eps_bot_sol_for_neg_kappa, eps_bot_sol_for_pos_kappa]) return res else: # Array solution for the whole kappa_t res = root(lambda eps_bot_t: self.get_N_t(self.kappa_t, eps_bot_t), 0.0000001 + np.zeros_like(self.kappa_t), tol=1e-6) if not res.success: print('No solution', res.message) return res.x def _get_eps_bot_piecewise_sol(self, kappa_pos=True): if kappa_pos: kappas = self.kappa_t[np.where(self.kappa_t >= 0)] else: kappas = self.kappa_t[np.where(self.kappa_t < 0)] res = [] if kappa_pos: init_guess = 0.00001 kappa_loop_list = kappas else: init_guess = -0.00001 kappa_loop_list = reversed(kappas) for kappa in kappa_loop_list: sol = root( lambda eps_bot: self.get_N_t(np.array([kappa]), eps_bot), np.array([init_guess]), tol=1e-6).x[0] # This condition is to avoid having init_guess~0 which causes non-convergence if abs(sol) > 1e-5: init_guess = sol res.append(sol) if kappa_pos: return res else: return list(reversed(res)) # POSTPROCESSING kappa_cr = tr.Property(depends_on=DEPSTR) '''Curvature at which a critical strain is attained at the eps_bot''' @tr.cached_property def _get_kappa_cr(self): res = root(lambda kappa: self.get_N_t(kappa, self.eps_cr), 0.0000001 + np.zeros_like(self.eps_cr), tol=1e-10) if not res.success: print('No kappa_cr solution (for plot_norm() function)', res.message) return res.x M_s_t = tr.Property(depends_on=DEPSTR) '''Bending moment (steel) ''' @tr.cached_property def _get_M_s_t(self): if len(self.z_j) == 0: return np.zeros_like(self.kappa_t) eps_z_tj = self.symb.get_eps_z(self.kappa_t[:, np.newaxis], self.eps_bot_t[:, np.newaxis], self.z_j[np.newaxis, :]) # Get the crack bridging force in each reinforcement layer # given the corresponding crack-bridge law. N_tj = self.cross_section_layout.get_N_tj(eps_z_tj) return -np.einsum('tj,j->t', N_tj, self.z_j) M_c_t = tr.Property(depends_on=DEPSTR) '''Bending moment (concrete) ''' @tr.cached_property def _get_M_c_t(self): z_tm = self.z_m[np.newaxis, :] b_z_m = self.cross_section_shape_.get_b(z_tm) N_z_tm2 = b_z_m * self.get_sig_c_z(self.kappa_t, self.eps_bot_t, z_tm) return -np.trapz(N_z_tm2 * z_tm, x=z_tm, axis=-1) M_t = tr.Property(depends_on=DEPSTR) '''Bending moment ''' @tr.cached_property def _get_M_t(self): # print('M - k recalculated') eta_factor = 1. return eta_factor * (self.M_c_t + self.M_s_t) # @tr.cached_property # def _get_M_t(self): # initial_step = (self.high_kappa - self.low_kappa) / self.num_of_trials # for i in range(self.num_of_trials): # try: # M_t = self.M_c_t + self.M_s_t # except SolutionNotFoundError: # print('failed high_kappa: ', self.high_kappa) # self.high_kappa -= initial_step # else: # # This will run when no exception has been received # print('success high_kappa: ', self.high_kappa) # if i == 0: # print('Note: high_kappa success from 1st try! selecting a higher value for high_kappa may produce ' # 'a more reliable result!') # return M_t # print('No solution has been found!') # return M_t N_s_tj = tr.Property(depends_on=DEPSTR) '''Normal forces (steel) ''' @tr.cached_property def _get_N_s_tj(self): return self.get_N_s_tj(self.kappa_t, self.eps_bot_t) eps_tm = tr.Property(depends_on=DEPSTR) '''strain profiles ''' @tr.cached_property def _get_eps_tm(self): return self.symb.get_eps_z(self.kappa_t[:, np.newaxis], self.eps_bot_t[:, np.newaxis], self.z_m[np.newaxis, :]) sig_tm = tr.Property(depends_on=DEPSTR) '''strain profiles ''' @tr.cached_property def _get_sig_tm(self): return self.get_sig_c_z(self.kappa_t, self.eps_bot_t, self.z_m[np.newaxis, :]) M_norm = tr.Property(depends_on=DEPSTR) ''' ''' @tr.cached_property def _get_M_norm(self): # Section modulus @TODO optimize W for var b W = (self.b * self.H**2) / 6 sig_cr = self.E_ct * self.eps_cr return W * sig_cr kappa_norm = tr.Property() def _get_kappa_norm(self): return self.kappa_cr inv_M_kappa = tr.Property(depends_on=DEPSTR) '''Return the inverted data points ''' @tr.cached_property def _get_inv_M_kappa(self): try: """cut off the descending tails""" M_t = self.M_t I_max = np.argmax(M_t) I_min = np.argmin(M_t) M_I = np.copy(M_t[I_min:I_max + 1]) kappa_I = np.copy(self.kappa_t[I_min:I_max + 1]) # find the index corresponding to zero kappa idx = np.argmax(0 <= kappa_I) # and modify the values such that the # Values of moment are non-descending M_plus = M_I[idx:] M_diff = M_plus[:, np.newaxis] - M_plus[np.newaxis, :] n_ij = len(M_plus) ij = np.mgrid[0:n_ij:1, 0:n_ij:1] M_diff[np.where(ij[1] >= ij[0])] = 0 i_x = np.argmin(M_diff, axis=1) M_I[idx:] = M_plus[i_x] return M_I, kappa_I except ValueError: print( 'M inverse has not succeeded, the M-Kappa solution may have failed due to ' 'a wrong kappa range or not suitable material law!') return np.array([0]), np.array([0]) def get_kappa_M(self, M): M_I, kappa_I = self.inv_M_kappa return np.interp(M, M_I, kappa_I) def plot_norm(self, ax1, ax2): idx = self.idx ax1.plot(self.kappa_t / self.kappa_norm, self.M_t / self.M_norm) ax1.plot(self.kappa_t[idx] / self.kappa_norm, self.M_t[idx] / self.M_norm, marker='o') ax2.barh(self.z_j, self.N_s_tj[idx, :], height=2, color='red', align='center') # ax2.fill_between(eps_z_arr[idx,:], z_arr, 0, alpha=0.1); ax3 = ax2.twiny() # ax3.plot(self.eps_tm[idx, :], self.z_m, color='k', linewidth=0.8) ax3.plot(self.sig_tm[idx, :], self.z_m) ax3.axvline(0, linewidth=0.8, color='k') ax3.fill_betweenx(self.z_m, self.sig_tm[idx, :], 0, alpha=0.1) mpl_align_xaxis(ax2, ax3) M_scale = Float(1e+6) plot_strain = Bool(False) def plot(self, ax1, ax2, ax3): self.plot_mk_and_stress_profile(ax1, ax2) if self.plot_strain: self.plot_strain_profile(ax3) else: self.plot_mk_inv(ax3) @staticmethod def subplots(fig): ax1, ax2, ax3 = fig.subplots(1, 3) return ax1, ax2, ax3 def update_plot(self, axes): self.plot(*axes) def plot_mk_inv(self, ax3): try: M, kappa = self.inv_M_kappa ax3.plot(M / self.M_scale, kappa) except ValueError: print( 'M inverse has not succeeded, the M-Kappa solution may have failed due to a wrong kappa range!' ) ax3.set_xlabel('Moment [kNm]') ax3.set_ylabel('Curvature[mm$^{-1}$]') def plot_mk_and_stress_profile(self, ax1, ax2): self.plot_mk(ax1) idx = self.idx ax1.plot(self.kappa_t[idx], self.M_t[idx] / self.M_scale, color='orange', marker='o') if len(self.z_j): ax2.barh(self.z_j, self.N_s_tj[idx, :] / self.A_j, height=4, color='red', align='center') ax2.set_ylabel('z [mm]') ax2.set_xlabel('$\sigma_r$ [MPa]') ax22 = ax2.twiny() ax22.set_xlabel('$\sigma_c$ [MPa]') ax22.plot(self.sig_tm[idx, :], self.z_m) ax22.axvline(0, linewidth=0.8, color='k') ax22.fill_betweenx(self.z_m, self.sig_tm[idx, :], 0, alpha=0.1) mpl_align_xaxis(ax2, ax22) def plot_mk(self, ax1): ax1.plot(self.kappa_t, self.M_t / self.M_scale, label='M-K') ax1.set_ylabel('Moment [kNm]') ax1.set_xlabel('Curvature [mm$^{-1}$]') ax1.legend() def plot_strain_profile(self, ax): ax.set_ylabel('z [mm]') ax.set_xlabel(r'$\varepsilon$ [-]') ax.plot(self.eps_tm[self.idx, :], self.z_m) ax.axvline(0, linewidth=0.8, color='k') ax.fill_betweenx(self.z_m, self.eps_tm[self.idx, :], 0, alpha=0.1) def get_mk(self): return self.M_t / self.M_scale, self.kappa_t
class MSX(MATS3DEval): name = 'MSX' double_pvw = Bool(True, MAT=True) ipw_view = View(Item('E'), Item('nu'), Item('double_pvw'), Item('eps_max'), Item('n_eps')) mic = EitherType(options=[('contim', VCoNTIM), ('untim', VUNTIM), ('dntim', VDNTIM)], on_option_change='reset_mic') integ_scheme = EitherType(options=[('3DM28', MSIS3DM28)]) @tr.on_trait_change('E, nu') def _set_E(self, event): self.reset_mic() def reset_mic(self): self.mic_.E_N = self.E / (1.0 - 2.0 * self.nu) self.mic_.E_T = self.E * (1.0 - 4 * self.nu) / \ ((1.0 + self.nu) * (1.0 - 2 * self.nu)) tree = ['mic', 'integ_scheme'] state_var_shapes = tr.Property(depends_on='mic, integ_scheme') @tr.cached_property def _get_state_var_shapes(self): sv_shapes = { name: (self.integ_scheme_.n_mp, ) + shape for name, shape in self.mic_.state_var_shapes.items() } return sv_shapes def _get_e_a(self, eps_ab): """ Get the microplane projected strains """ # get the normal strain array for each microplane e_N = np.einsum('nij,...ij->...n', self.integ_scheme_.MPNN, eps_ab) # get the tangential strain vector array for each microplane MPTT_ijr = self.integ_scheme_.MPTT e_T_a = np.einsum('nija,...ij->...na', MPTT_ijr, eps_ab) return np.concatenate([e_N[..., np.newaxis], e_T_a], axis=-1) def _get_beta_abcd(self, eps_ab, omega_N, omega_T, **Eps): """ Returns the 4th order damage tensor 'beta4' using (cf. [Baz99], Eq.(63)) """ MPW = self.integ_scheme_.MPW MPN = self.integ_scheme_.MPN delta = np.identity(3) beta_N = np.sqrt(1. - omega_N) beta_T = np.sqrt(1. - omega_T) beta_ijkl = ( np.einsum('n,...n,ni,nj,nk,nl->...ijkl', MPW, beta_N, MPN, MPN, MPN, MPN) + 0.25 * (np.einsum('n,...n,ni,nk,jl->...ijkl', MPW, beta_T, MPN, MPN, delta) + np.einsum('n,...n,ni,nl,jk->...ijkl', MPW, beta_T, MPN, MPN, delta) + np.einsum('n,...n,nj,nk,il->...ijkl', MPW, beta_T, MPN, MPN, delta) + np.einsum('n,...n,nj,nl,ik->...ijkl', MPW, beta_T, MPN, MPN, delta) - 4.0 * np.einsum('n,...n,ni,nj,nk,nl->...ijkl', MPW, beta_T, MPN, MPN, MPN, MPN))) return beta_ijkl def NT_to_ab(self, v_N, v_T_a): """ Integration of the (inelastic) strains for each microplane """ MPW = self.integ_scheme_.MPW MPN = self.integ_scheme_.MPN delta = np.identity(3) # 2-nd order plastic (inelastic) tensor tns_ab = (np.einsum('n,...n,na,nb->...ab', MPW, v_N, MPN, MPN) + 0.5 * (np.einsum('n,...nf,na,fb->...ab', MPW, v_T_a, MPN, delta) + np.einsum('n,...nf,nb,fa->...ab', MPW, v_T_a, MPN, delta))) return tns_ab def get_corr_pred(self, eps_ab, t_n1, **Eps): """ Corrector predictor computation. """ # ------------------------------------------------------------------ # Damage tensor (4th order) using product- or sum-type symmetrization: # ------------------------------------------------------------------ eps_a = self._get_e_a(eps_ab) sig_a, D_ab = self.mic_.get_corr_pred(eps_a, t_n1, **Eps) beta_abcd = self._get_beta_abcd(eps_ab, **Eps) # ------------------------------------------------------------------ # Damaged stiffness tensor calculated based on the damage tensor beta4: # ------------------------------------------------------------------ D_abcd = np.einsum('...ijab, abef, ...cdef->...ijcd', beta_abcd, self.D_abef, beta_abcd) if self.double_pvw: # ---------------------------------------------------------------------- # Return stresses (corrector) and damaged secant stiffness matrix (predictor) # ---------------------------------------------------------------------- eps_p_a = self.mic_.get_eps_NT_p(**Eps) if eps_p_a: eps_N_p, eps_T_p_a = eps_p_a eps_p_ab = self.NT_to_ab(eps_N_p, eps_T_p_a) eps_e_ab = eps_ab - eps_p_ab else: eps_e_ab = eps_ab sig_ab = np.einsum('...abcd,...cd->...ab', D_abcd, eps_e_ab) else: sig_N, sig_T_a = sig_a[..., 0], sig_a[..., 1:] sig_ab = self.NT_to_ab(sig_N, sig_T_a) return sig_ab, D_abcd def update_plot(self, axes): ax_sig, ax_d_sig = axes eps_max = self.eps_max n_eps = self.n_eps eps11_range = np.linspace(1e-9, eps_max, n_eps) eps_range = np.zeros((n_eps, 3, 3)) eps_range[:, 0, 0] = eps11_range state_vars = { var: np.zeros((1, ) + shape) for var, shape in self.state_var_shapes.items() } sig11_range, d_sig1111_range = [], [] for eps_ab in eps_range: try: sig_ab, D_range = self.get_corr_pred(eps_ab[np.newaxis, ...], 1, **state_vars) except ReturnMappingError: break sig11_range.append(sig_ab[0, 0, 0]) d_sig1111_range.append(D_range[0, 0, 0, 0, 0]) sig11_range = np.array(sig11_range, dtype=np.float_) eps11_range = eps11_range[:len(sig11_range)] ax_sig.plot(eps11_range, sig11_range, color='blue') d_sig1111_range = np.array(d_sig1111_range, dtype=np.float_) ax_d_sig.plot(eps11_range, d_sig1111_range, linestyle='dashed', color='gray') ax_sig.set_xlabel(r'$\varepsilon_{11}$ [-]') ax_sig.set_ylabel(r'$\sigma_{11}$ [MPa]') ax_d_sig.set_ylabel( r'$\mathrm{d} \sigma_{11} / \mathrm{d} \varepsilon_{11}$ [MPa]') ax_d_sig.plot(eps11_range[:-1], (sig11_range[:-1] - sig11_range[1:]) / (eps11_range[:-1] - eps11_range[1:]), color='orange', linestyle='dashed')
class MS1EEQ(MATS3DEval, InteractiveModel): gamma_T = tr.Float(1000000., label="gamma_T", desc=" Tangential Kinematic hardening modulus", enter_set=True, auto_set=False) K_T = tr.Float(10000., label="K_T", desc="Tangential Isotropic harening", enter_set=True, auto_set=False) S_T = tr.Float(0.005, label="S_T", desc="Damage strength", enter_set=True, auto_set=False) r_T = tr.Float(9., label="r", desc="Damage cumulation parameter", enter_set=True, auto_set=False) p_T = tr.Float(2., label="p_T", desc="Damage cumulation parameter", enter_set=True, auto_set=False) c_T = tr.Float(3, label="c_T", desc="Damage cumulation parameter", enter_set=True, auto_set=False) sigma_T_0 = tr.Float(1.7, label="sigma_T_0", desc="Reversibility limit", enter_set=True, auto_set=False) m_T = tr.Float(0.1, label="m_T", desc="Lateral pressure coefficient", enter_set=True, auto_set=False) # ------------------------------------------- # Normal_Tension constitutive law parameters (without cumulative normal strain) # ------------------------------------------- Ad = tr.Float(10.0, label="A_d", desc="brittleness coefficient", enter_set=True, auto_set=False) eps_0 = tr.Float(.0001, label="eps_N_0", desc="threshold strain", enter_set=True, auto_set=False) # ----------------------------------------------- # Normal_Compression constitutive law parameters # ----------------------------------------------- K_N = tr.Float(10000., label="K_N", desc=" Normal isotropic harening", enter_set=True, auto_set=False) gamma_N = tr.Float(5000., label="gamma_N", desc="Normal kinematic hardening", enter_set=True, auto_set=False) sigma_N_0 = tr.Float(10., label="sigma_N_0", desc="Yielding stress", enter_set=True, auto_set=False) # ------------------------------------------------------------------------- # Cached elasticity tensors # ------------------------------------------------------------------------- E = tr.Float(35e+3, label="E", desc="Young's Modulus", auto_set=False, input=True) nu = tr.Float(0.2, label='nu', desc="Poison ratio", auto_set=False, input=True) ipw_view = View( Item('gamma_T', latex=r'\gamma_\mathrm{T}', minmax=(10, 100000)), Item('K_T', latex=r'K_\mathrm{T}', minmax=(10, 10000)), Item('S_T', latex=r'S_\mathrm{T}', minmax=(0.001, 0.01)), Item('r_T', latex=r'r_\mathrm{T}', minmax=(1, 3)), Item('p_T', latex=r'e_\mathrm{T}', minmax=(1, 40)), Item('c_T', latex=r'c_\mathrm{T}', minmax=(1, 10)), Item('sigma_T_0', latex=r'\bar{sigma}^\pi_{T}', minmax=(1, 10)), Item('m_T', latex=r'm_\mathrm{T}', minmax=(0.001, 3)), ) n_D = 3 state_var_shapes = tr.Property @tr.cached_property def _get_state_var_shapes(self): dictionay = { name: (self.n_mp, ) + shape for name, shape in self.mic_state_var_shapes.items() } dictionay_macro = { name: shape for name, shape in self.mac_state_var_shapes.items() } dictionay.update(dictionay_macro) return dictionay mic_state_var_shapes = dict( omega_N_Emn=(), # damage N z_N_Emn=(), alpha_N_Emn=(), r_N_Emn=(), eps_N_p_Emn=(), sigma_N_Emn=(), omega_T_Emn=(), z_T_Emn=(), alpha_T_Emna=(n_D, ), eps_T_pi_Emna=(n_D, ), sigma_T_Emna=(n_D, ), plastic_dissip_T_Emn=(), damage_dissip_T_Emn=(), plastic_dissip_N_Emn=(), damage_dissip_N_Emn=(), ) mac_state_var_shapes = dict( total_work_microplane=(), total_work_macro=(), eps_aux=( n_D, n_D, ), ) ''' State variables 1) damage N, 2) iso N, 3) kin N, 4) consolidation N, 5) eps p N, 6) sigma N, 7) iso F N, 8) kin F N, 9) energy release N, 10) damage T, 11) iso T, 12-13) kin T, 14-15) eps p T, 16-17) sigma T, 18) iso F T, 19-20) kin F T, 21) energy release T ''' # -------------------------------------------------------------- # microplane constitutive law (normal behavior CP + TD) # (without cumulative normal strain for fatigue under tension) # -------------------------------------------------------------- def get_normal_law(self, eps_N_Emn, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux): E_N = self.E / (1.0 - 2.0 * self.nu) sigma_N_Emn_tilde = E_N * (eps_N_Emn - eps_N_p_Emn) r_N_Emn_aux = copy.deepcopy(r_N_Emn) omega_N_Emn_aux = copy.deepcopy(omega_N_Emn) pos = sigma_N_Emn_tilde > 1e-6 # microplanes under tension pos2 = sigma_N_Emn_tilde < -1e-6 # microplanes under compression tension = 1.0 * pos compression = 1.0 * pos2 # thermo forces Z = self.K_N * z_N_Emn * compression X = self.gamma_N * alpha_N_Emn * compression h = (self.sigma_N_0 + Z) * compression f_trial = (abs(sigma_N_Emn_tilde - X) - h) * compression # threshold plasticity thres_1 = f_trial > 1e-10 delta_lamda = f_trial / \ (E_N / (1 - omega_N_Emn) + abs(self.K_N) + self.gamma_N) * thres_1 eps_N_p_Emn += delta_lamda * \ np.sign(sigma_N_Emn_tilde - X) z_N_Emn += delta_lamda alpha_N_Emn += delta_lamda * \ np.sign(sigma_N_Emn_tilde - X) def R_N(r_N_Emn): return (1.0 / self.Ad) * (-r_N_Emn / (1.0 + r_N_Emn)) Y_N = 0.5 * tension * E_N * (eps_N_Emn - eps_N_p_Emn)**2.0 Y_0 = 0.5 * E_N * self.eps_0**2.0 f = (Y_N - (Y_0 + R_N(r_N_Emn))) * tension # threshold damage thres_2 = f > 1e-6 def f_w(Y): return 1.0 - 1.0 / (1.0 + self.Ad * (Y - Y_0)) omega_N_Emn[f > 1e-6] = f_w(Y_N)[f > 1e-6] omega_N_Emn[...] = np.clip(omega_N_Emn, 0, 1.0) r_N_Emn[f > 1e-6] = -omega_N_Emn[f > 1e-6] sigma_N_Emn[...] = (1.0 - tension * omega_N_Emn) * E_N * (eps_N_Emn - eps_N_p_Emn) Z = self.K_N * z_N_Emn * compression X = self.gamma_N * alpha_N_Emn * compression # sigma_N_Emn = E_N * (eps_N_Emn - eps_N_p_Emn) # pos1 = [(eps_N_Emn < -1e-6) & (sigma_trial > 1e-6)] # looking for microplanes violating strain boundary # sigma_N_Emn[pos1[0]] = 0 delta_eps_N_p_Emn = np.zeros_like(eps_N_p_Emn) delta_alpha_N_Emn = np.zeros_like(alpha_N_Emn) delta_z_N_Emn = np.zeros_like(z_N_Emn) delta_omega_N_Emn = np.zeros_like(omega_N_Emn) delta_r_N_Emn = np.zeros_like(r_N_Emn) delta_eps_N_p_Emn = delta_lamda * \ np.sign(sigma_N_Emn_tilde - X) delta_alpha_N_Emn = delta_lamda * \ np.sign(sigma_N_Emn_tilde - X) delta_z_N_Emn = delta_lamda delta_omega_N_Emn = omega_N_Emn - omega_N_Emn_aux delta_r_N_Emn = r_N_Emn - r_N_Emn_aux plastic_dissip_N_Emn[...] = np.einsum('...n,...n->...n', sigma_N_Emn, delta_eps_N_p_Emn) - \ np.einsum('...n,...n->...n', X, delta_alpha_N_Emn) - np.einsum('...n,...n->...n', Z, delta_z_N_Emn) damage_dissip_N_Emn[...] = np.einsum('...n,...n->...n', Y_N, delta_omega_N_Emn) - \ np.einsum('...n,...n->...n', R_N(r_N_Emn), delta_r_N_Emn) return sigma_N_Emn, Z, X, Y_N # ------------------------------------------------------------------------- # microplane constitutive law (Tangential CSD)-(Pressure sensitive cumulative damage) # ------------------------------------------------------------------------- def get_tangential_law(self, eps_T_Emna, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux): E_T = self.E * (1.0 - 4 * self.nu) / \ ((1.0 + self.nu) * (1.0 - 2 * self.nu)) # thermo forces 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 = np.sqrt( np.einsum('...na,...na->...n', (sig_pi_trial - X), (sig_pi_trial - X))) Y = 0.5 * E_T * \ np.einsum( '...na,...na->...n', (eps_T_Emna - eps_T_pi_Emna), (eps_T_Emna - eps_T_pi_Emna)) E_N = self.E / (1.0 - 2.0 * self.nu) eps_N_Emn = self._get_e_N_Emn(eps_Emab) sigma_N_Emn = (1.0 - omega_N_Emn) * E_N * (eps_N_Emn - eps_N_p_Emn) f = norm_1 - self.sigma_T_0 - Z + self.m_T * sigma_N_Emn plas_1 = f > 1e-15 elas_1 = f < 1e-15 delta_lamda = f / \ (E_T / (1.0 - omega_T_Emn) + self.gamma_T + self.K_T) * plas_1 norm_2 = 1.0 * elas_1 + np.sqrt( np.einsum('...na,...na->...n', (sig_pi_trial - X), (sig_pi_trial - X))) * plas_1 eps_T_pi_Emna[..., 0] += plas_1 * delta_lamda * \ ((sig_pi_trial[..., 0] - X[..., 0]) / (1.0 - omega_T_Emn)) / norm_2 eps_T_pi_Emna[..., 1] += plas_1 * delta_lamda * \ ((sig_pi_trial[..., 1] - X[..., 1]) / (1.0 - omega_T_Emn)) / norm_2 eps_T_pi_Emna[..., 2] += plas_1 * delta_lamda * \ ((sig_pi_trial[..., 2] - X[..., 2]) / (1.0 - omega_T_Emn)) / norm_2 omega_T_Emn += plas_1 * ((1 - omega_T_Emn) ** self.c_T) * \ (delta_lamda * (Y / self.S_T) ** self.r_T) * \ (self.sigma_T_0 / (self.sigma_T_0 - self.m_T * sigma_N_Emn)) ** self.p_T omega_T_Emn[...] = np.clip(omega_T_Emn, 0, 1.0) alpha_T_Emna[..., 0] += plas_1 * delta_lamda * \ (sig_pi_trial[..., 0] - X[..., 0]) / norm_2 alpha_T_Emna[..., 1] += plas_1 * delta_lamda * \ (sig_pi_trial[..., 1] - X[..., 1]) / norm_2 alpha_T_Emna[..., 2] += plas_1 * delta_lamda * \ (sig_pi_trial[..., 2] - X[..., 2]) / norm_2 z_T_Emn += plas_1 * delta_lamda sigma_T_Emna[...] = np.einsum('...n,...na->...na', (1 - omega_T_Emn), E_T * (eps_T_Emna - eps_T_pi_Emna)) Z = self.K_T * z_T_Emn X = self.gamma_T * alpha_T_Emna Y = 0.5 * E_T * \ np.einsum( '...na,...na->...n', (eps_T_Emna - eps_T_pi_Emna), (eps_T_Emna - eps_T_pi_Emna)) # Energy dissipation evaluation delta_eps_T_pi_Emna = np.zeros_like(eps_T_pi_Emna) delta_alpha_T_Emna = np.zeros_like(alpha_T_Emna) delta_z_T_Emn = np.zeros_like(z_T_Emn) delta_omega_T_Emn = np.zeros_like(omega_T_Emn) delta_eps_T_pi_Emna[..., 0] = plas_1 * delta_lamda * \ ((sig_pi_trial[..., 0] - X[..., 0]) / (1.0 - omega_T_Emn)) / norm_2 delta_eps_T_pi_Emna[..., 1] = plas_1 * delta_lamda * \ ((sig_pi_trial[..., 1] - X[..., 1]) / (1.0 - omega_T_Emn)) / norm_2 delta_eps_T_pi_Emna[..., 2] = plas_1 * delta_lamda * \ ((sig_pi_trial[..., 2] - X[..., 2]) / (1.0 - omega_T_Emn)) / norm_2 delta_alpha_T_Emna[..., 0] = plas_1 * delta_lamda * \ (sig_pi_trial[..., 0] - X[..., 0]) / norm_2 delta_alpha_T_Emna[..., 1] = plas_1 * delta_lamda * \ (sig_pi_trial[..., 1] - X[..., 1]) / norm_2 delta_alpha_T_Emna[..., 2] = plas_1 * delta_lamda * \ (sig_pi_trial[..., 2] - X[..., 2]) / norm_2 delta_z_T_Emn = plas_1 * delta_lamda delta_omega_T_Emn = plas_1 * ((1 - omega_T_Emn) ** self.c_T) * \ (delta_lamda * (Y / self.S_T) ** self.r_T) * \ (self.sigma_T_0 / (self.sigma_T_0 - self.m_T * sigma_N_Emn)) ** self.p_T plastic_dissip_T_Emn[...] = np.einsum('...na,...na->...n', sigma_T_Emna, delta_eps_T_pi_Emna) #- \ #np.einsum('...na,...na->...n', X, delta_alpha_T_Emna) - np.einsum('...n,...n->...n', Z, delta_z_T_Emn) damage_dissip_T_Emn[...] = np.einsum('...n,...n->...n', Y, delta_omega_T_Emn) # if plastic_dissip_T_Emn.any() < -1e-15: # print(sigma_T_Emna, delta_eps_T_pi_Emna) return sigma_T_Emna, Z, X, Y, plastic_dissip_T_Emn # #------------------------------------------------------------------------- # # MICROPLANE-Kinematic constraints # #------------------------------------------------------------------------- # ------------------------------------------------- # get the operator of the microplane normals _MPNN = tr.Property(depends_on='n_mp') @tr.cached_property def _get__MPNN(self): MPNN_nij = np.einsum('ni,nj->nij', self._MPN, self._MPN) return MPNN_nij # get the third order tangential tensor (operator) for each microplane _MPTT = tr.Property(depends_on='n_mp') @tr.cached_property def _get__MPTT(self): delta = self.DELTA 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 def _get_e_N_Emn(self, eps_Emab): # get the normal strain array for each microplane return np.einsum('nij,...ij->...n', self._MPNN, eps_Emab) def _get_e_T_Emna(self, eps_Emab): # get the tangential strain vector array for each microplane MPTT_ijr = self._get__MPTT() return np.einsum('nija,...ij->...na', MPTT_ijr, eps_Emab) # --------------------------------------------------------------------- # Extra homogenization of damage tensor in case of two damage parameters # Returns the 4th order damage tensor 'beta4' using (ref. [Baz99], Eq.(63)) # --------------------------------------------------------------------- def _get_beta_Emabcd(self, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux): # Returns the 4th order damage tensor 'beta4' using # (cf. [Baz99], Eq.(63)) eps_N_Emn = self._get_e_N_Emn(eps_Emab) eps_T_Emna = self._get_e_T_Emna(eps_Emab) sigma_N_Emn, Z, X, Y_N = self.get_normal_law( eps_N_Emn, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux) # sliding tangential strains sigma_T_Emna, Z_T, X_T, Y_T, plastic_dissip_T_Emn = self.get_tangential_law( eps_T_Emna, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux) delta = self.DELTA beta_N = np.sqrt(1. - omega_N_Emn) beta_T = np.sqrt(1. - omega_T_Emn) beta_ijkl = np.einsum('n, ...n,ni, nj, nk, nl -> ...ijkl', self._MPW, beta_N, self._MPN, self._MPN, self._MPN, self._MPN) + \ 0.25 * (np.einsum('n, ...n,ni, nk, jl -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) + np.einsum('n, ...n,ni, nl, jk -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) + np.einsum('n, ...n,nj, nk, il -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) + np.einsum('n, ...n,nj, nl, ik -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, delta) - 4.0 * np.einsum('n, ...n, ni, nj, nk, nl -> ...ijkl', self._MPW, beta_T, self._MPN, self._MPN, self._MPN, self._MPN)) return beta_ijkl def _get_phi(self, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, plastic_dissip_T_Emn): phi_n = np.sqrt(1.0 - omega_N_Emn) * np.sqrt(1.0 - omega_T_Emn) phi_ab = np.einsum('...n,n,nab->...ab', phi_n, self._MPW, self._MPNN) return phi_ab DELTA = np.identity(n_D) # ----------------------------------------------------------- # Integration of the (inelastic) strains for each microplane # ----------------------------------------------------------- def _get_eps_p_Emab(self, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux): eps_N_Emn = self._get_e_N_Emn(eps_Emab) eps_T_Emna = self._get_e_T_Emna(eps_Emab) sigma_N_Emn, Z, X, Y_N = self.get_normal_law( eps_N_Emn, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux) # sliding tangential strains sigma_T_Emna, Z_T, X_T, Y_T, plastic_dissip_T_Emn = self.get_tangential_law( eps_T_Emna, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux) # 2-nd order plastic (inelastic) tensor eps_p_Emab = (np.einsum('n,...n,na,nb->...ab', self._MPW, eps_N_p_Emn, self._MPN, self._MPN) + 0.5 * (np.einsum('n,...nf,na,fb->...ab', self._MPW, eps_T_pi_Emna, self._MPN, self.DELTA) + np.einsum('n,...nf,nb,fa->...ab', self._MPW, eps_T_pi_Emna, self._MPN, self.DELTA))) return eps_p_Emab def get_total_work(self, eps_Emab, sig_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux): delta_eps_Emab = eps_Emab - eps_aux delta_eps_N_Emn = self._get_e_N_Emn(eps_Emab) - self._get_e_N_Emn( eps_aux) delta_eps_T_Emna = self._get_e_T_Emna(eps_Emab) - self._get_e_T_Emna( eps_aux) # work_microplane = np.einsum('...n,...n->...n', sigma_N_Emn, delta_eps_N_Emn) + np.einsum('...na,...na->...n', # sigma_T_Emna, # delta_eps_T_Emna) delta_eps_vector = np.einsum('...na,...n->...na', self._MPN, delta_eps_N_Emn) + delta_eps_T_Emna sigma_vector = np.einsum('...na,...n->...na', self._MPN, sigma_N_Emn) + sigma_T_Emna work_microplane = np.einsum('...na,...na->...n', sigma_vector, delta_eps_vector) total_work_microplane[...] = np.einsum('...n,...n->...', self._MPW, work_microplane) total_work_macro[...] = np.einsum('...ij,...ij->...', sig_Emab, delta_eps_Emab) eps_aux[...] = eps_Emab return total_work_microplane, total_work_macro def get_corr_pred(self, eps_Emab, t_n1, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux): """Evaluation - get the corrector and predictor """ # Corrector predictor computation. eps_N_Emn = self._get_e_N_Emn(eps_Emab) eps_T_Emna = self._get_e_T_Emna(eps_Emab) sigma_N_Emn, Z, X, Y_N = self.get_normal_law( eps_N_Emn, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux) # sliding tangential strains sigma_T_Emna, Z_T, X_T, Y_T, plastic_dissip_T_Emn = self.get_tangential_law( eps_T_Emna, eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux) eps_N_Emn_effective = np.einsum('...n,...n->...n', eps_N_Emn, (1 - omega_N_Emn)) eps_T_Emna_effective = np.einsum('...na,...n->...na', eps_T_Emna, (1 - omega_T_Emn)) delta = np.identity(3) eps_Emab_effective = ( np.einsum('n,...n,na,nb->...ab', self._MPW, eps_N_Emn_effective, self._MPN, self._MPN) + 0.5 * (np.einsum('n,...nf,na,fb->...ab', self._MPW, eps_T_Emna_effective, self._MPN, delta) + np.einsum('n,...nf,nb,fa->...ab', self._MPW, eps_T_Emna_effective, self._MPN, delta))) sigma_Emab_effective = np.einsum('...abcd,...cd->...ab', self.D_abef, eps_Emab_effective) sigma_N_Emn_effective = self._get_e_N_Emn(sigma_Emab_effective) sigma_T_Emna_effective = self._get_e_T_Emna(sigma_Emab_effective) sigma_N_Emn[...] = np.einsum('...n,...n->...n', sigma_N_Emn_effective, (1 - omega_N_Emn)) sigma_T_Emna[...] = np.einsum('...na,...n->...na', sigma_T_Emna_effective, (1 - omega_T_Emn)) sig_Emab = (np.einsum('n,...n,na,nb->...ab', self._MPW, sigma_N_Emn, self._MPN, self._MPN) + 0.5 * (np.einsum('n,...nf,na,fb->...ab', self._MPW, sigma_T_Emna, self._MPN, delta) + np.einsum('n,...nf,nb,fa->...ab', self._MPW, sigma_T_Emna, self._MPN, delta))) # ------------------------------------------------------------------ # Damage tensor (4th order) using product- or sum-type symmetrization: # ------------------------------------------------------------------ beta_Emabcd = self._get_beta_Emabcd( eps_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux) # ------------------------------------------------------------------ # Damaged stiffness tensor calculated based on the damage tensor beta4: # ------------------------------------------------------------------ D_Emabcd = np.einsum('...ijab, abef, ...cdef->...ijcd', beta_Emabcd, self.D_abef, beta_Emabcd) total_work_micro, total_work_macro = self.get_total_work( eps_Emab, sig_Emab, omega_N_Emn, z_N_Emn, alpha_N_Emn, r_N_Emn, eps_N_p_Emn, sigma_N_Emn, omega_T_Emn, z_T_Emn, alpha_T_Emna, eps_T_pi_Emna, sigma_T_Emna, plastic_dissip_T_Emn, damage_dissip_T_Emn, plastic_dissip_N_Emn, damage_dissip_N_Emn, total_work_microplane, total_work_macro, eps_aux) return sig_Emab, D_Emabcd
class IShape(CrossSectionShapeBase): H = Float(900, GEO=True) B_w = Float(50, GEO=True) B_f_bot = Float(200, GEO=True) B_f_top = Float(200, GEO=True) H_f_bot = Float(145, GEO=True) H_f_top = Float(145, GEO=True) ipw_view = View( *CrossSectionShapeBase.ipw_view.content, Item('B_w', minmax=(10, 3000), latex=r'B_w \mathrm{[mm]}'), Item('B_f_bot', minmax=(10, 3000), latex=r'B_{f_{bot}} \mathrm{[mm]}'), Item('B_f_top', minmax=(10, 3000), latex=r'B_{f_{top}} \mathrm{[mm]}'), Item('H_f_bot', minmax=(10, 3000), latex=r'H_{f_{bot}} \mathrm{[mm]}'), Item('H_f_top', minmax=(10, 3000), latex=r'H_{f_{top}} \mathrm{[mm]}'), ) def get_cs_area(self): return self.B_f_bot * self.H_f_bot + self.B_f_top * self.H_f_top + self.B_w * ( self.H - self.H_f_top - self.H_f_bot) def get_cs_i(self): pass get_b = tr.Property(tr.Callable, depends_on='+input') @tr.cached_property def _get_get_b(self): z_ = sp.Symbol('z') b_p = sp.Piecewise((self.B_f_bot, z_ < self.H_f_bot), (self.B_w, z_ < self.H - self.H_f_top), (self.B_f_top, True)) return sp.lambdify(z_, b_p, 'numpy') def update_plot(self, ax): # Start drawing from bottom center of the cross section cs_points = np.array([[self.B_f_bot / 2, 0], [self.B_f_bot / 2, self.H_f_bot], [self.B_w / 2, self.H_f_bot], [self.B_w / 2, self.H - self.H_f_top], [self.B_f_top / 2, self.H - self.H_f_top], [self.B_f_top / 2, self.H], [-self.B_f_top / 2, self.H], [-self.B_f_top / 2, self.H - self.H_f_top], [-self.B_w / 2, self.H - self.H_f_top], [-self.B_w / 2, self.H_f_bot], [-self.B_f_bot / 2, self.H_f_bot], [-self.B_f_bot / 2, 0]]) cs = Polygon(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, TShape.get_cs_i(self)[0], color='white', s=self.B_w, marker="+") ax.autoscale_view() ax.set_aspect('equal') ax.annotate('Area = {} $mm^2$'.format(int(IShape.get_cs_area(self)), 0), xy=(0, self.H / 2), color='blue')
class VUNTIM(MATS3DEval): """ Vectorized uncoupled normal tngential interface model """ # ------------------------------------------------------------------------- # Elasticity # ------------------------------------------------------------------------- E_N = Float(10000, MAT=True) E_T = Float(1000, MAT=True) gamma_T = Float(100000., MAT=True) K_T = Float(10000., MAT=True) S_T = Float(0.005, MAT=True) r_T = Float(9., MAT=True) e_T = Float(12., MAT=True) c_T = Float(4.6, MAT=True) tau_pi_bar = Float(1.7, MAT=True) a = Float(0.003, MAT=True) # ------------------------------------------------------------------------------ # Normal_Tension constitutive law parameters (without cumulative normal strain) # ------------------------------------------------------------------------------ Ad = Float(100.0, MAT=True) eps_0 = Float(0.00008, MAT=True) # ----------------------------------------------- # Normal_Compression constitutive law parameters # ----------------------------------------------- K_N = Float(10000., MAT=True) gamma_N = Float(5000., MAT=True) sig_0 = Float(30., MAT=True) ipw_view = View( Item('E_N'), Item('E_T'), Item('Ad'), Item('eps_0'), Item('K_N'), Item('gamma_N'), Item('sig_0'), Item('gamma_T', latex=r'\gamma_\mathrm{T}', minmax=(10, 100000)), Item('K_T', latex=r'K_\mathrm{T}', minmax=(10, 10000)), Item('S_T', latex=r'S_\mathrm{T}', minmax=(0.001, 0.01)), Item('r_T', latex=r'r_\mathrm{T}', minmax=(1, 3)), Item('e_T', latex=r'e_\mathrm{T}', minmax=(1, 40)), Item('c_T', latex=r'c_\mathrm{T}', minmax=(1, 10)), Item('tau_pi_bar', latex=r'\bar{\sigma}^\pi_{T}', minmax=(1, 10)), Item('a', latex=r'a_\mathrm{T}', minmax=(0.001, 3)), ) n_D = 3 state_var_shapes = dict( omega_N=(), # damage N z_N=(), alpha_N=(), r_N=(), eps_N_p=(), sig_N=(), omega_T=(), z_T=(), alpha_T_a=(n_D, ), eps_T_p_a=(n_D, ), sig_T_a=(n_D, ), ) # -------------------------------------------------------------- # microplane constitutive law (normal behavior CP + TD) # -------------------------------------------------------------- def get_normal_law(self, eps_N, **Eps): omega_N, z_N, alpha_N, r_N, eps_N_p = [ Eps[key] for key in ['omega_N', 'z_N', 'alpha_N', 'r_N', 'eps_N_p'] ] E_N = self.E_N # When deciding if a microplane is in tensile or compression, we define a strain boundary such that that # sigN <= 0 if eps_N < 0, avoiding entering in the quadrant of compressive strains and traction sig_trial = E_N * (eps_N - eps_N_p) pos1 = [(eps_N < -1e-6) & (sig_trial > 1e-6) ] # looking for microplanes violating strain boundary sig_trial[pos1[0]] = 0 pos = sig_trial > 1e-6 # microplanes under traction pos2 = sig_trial < -1e-6 # microplanes under compression H = 1.0 * pos H2 = 1.0 * pos2 # thermo forces sig_N_tilde = E_N * (eps_N - eps_N_p) sig_N_tilde[pos1[0]] = 0 # imposing strain boundary Z = self.K_N * z_N X = self.gamma_N * alpha_N * H2 h = (self.sig_0 + Z) * H2 f_trial = (abs(sig_N_tilde - X) - h) * H2 # threshold plasticity thres_1 = f_trial > 1e-6 delta_lamda = f_trial / \ (E_N / (1 - omega_N) + abs(self.K_N) + self.gamma_N) * thres_1 eps_N_p = eps_N_p + delta_lamda * \ np.sign(sig_N_tilde - X) z_N = z_N + delta_lamda alpha_N = alpha_N + delta_lamda * \ np.sign(sig_N_tilde - X) def R_N(r_N): return (1.0 / self.Ad) * (-r_N / (1.0 + r_N)) Y_N = 0.5 * H * E_N * (eps_N - eps_N_p)**2.0 Y_0 = 0.5 * E_N * self.eps_0**2.0 f = (Y_N - (Y_0 + R_N(r_N))) # threshold damage thres_2 = f > 1e-6 def f_w(Y): return 1.0 - 1.0 / (1.0 + self.Ad * (Y - Y_0)) omega_N[f > 1e-6] = f_w(Y_N)[f > 1e-6] r_N[f > 1e-6] = -omega_N[f > 1e-6] sig_N = (1.0 - H * omega_N) * E_N * (eps_N - eps_N_p) pos1 = [(eps_N < -1e-6) & (sig_trial > 1e-6) ] # looking for microplanes violating strain boundary sig_N[pos1[0]] = 0 return sig_N # ------------------------------------------------------------------------- # microplane constitutive law (Tangential CSD)-(Pressure sensitive cumulative damage) # ------------------------------------------------------------------------- def get_tangential_law(self, eps_T_a, **Eps): omega_T, z_T, alpha_T_a, eps_T_p_a, sig_N, = [ Eps[key] for key in [ 'omega_T', 'z_T', 'alpha_T_a', 'eps_T_p_a', 'sig_N', ] ] E_T = self.E_T # thermodynamic forces sig_pi_trial = E_T * (eps_T_a - eps_T_p_a) Z = self.K_T * z_T X = self.gamma_T * alpha_T_a norm_1 = np.sqrt( np.einsum('...na,...na->...n', (sig_pi_trial - X), (sig_pi_trial - X))) Y = 0.5 * E_T * np.einsum('...na,...na->...n', (eps_T_a - eps_T_p_a), (eps_T_a - eps_T_p_a)) # threshold f = norm_1 - self.tau_pi_bar - Z + self.a * sig_N plas_1 = f > 1e-6 elas_1 = f < 1e-6 delta_lamda = f / (E_T / (1.0 - omega_T) + self.gamma_T + self.K_T) * plas_1 norm_2 = 1.0 * elas_1 + np.sqrt( np.einsum('...na,...na->...n', (sig_pi_trial - X), (sig_pi_trial - X))) * plas_1 eps_T_p_a[..., 0] += plas_1 * delta_lamda * \ ((sig_pi_trial[..., 0] - X[..., 0]) / (1.0 - omega_T)) / norm_2 eps_T_p_a[..., 1] += plas_1 * delta_lamda * \ ((sig_pi_trial[..., 1] - X[..., 1]) / (1.0 - omega_T)) / norm_2 eps_T_p_a[..., 2] += plas_1 * delta_lamda * \ ((sig_pi_trial[..., 2] - X[..., 2]) / (1.0 - omega_T)) / norm_2 omega_T[...] += ((1.0 - omega_T) ** self.c_T) * \ (delta_lamda * (Y / self.S_T) ** self.r_T) * \ (self.tau_pi_bar / (self.tau_pi_bar - self.a * sig_N)) ** self.e_T alpha_T_a[..., 0] += plas_1 * delta_lamda * \ (sig_pi_trial[..., 0] - X[..., 0]) / norm_2 alpha_T_a[..., 1] += plas_1 * delta_lamda * \ (sig_pi_trial[..., 1] - X[..., 1]) / norm_2 alpha_T_a[..., 2] += plas_1 * delta_lamda * \ (sig_pi_trial[..., 2] - X[..., 2]) / norm_2 z_T[...] += delta_lamda sig_T_a = np.einsum('...n,...na->...na', (1 - omega_T), E_T * (eps_T_a - eps_T_p_a)) return sig_T_a def get_corr_pred(self, eps_a, t_n1, **Eps): eps_a_ = np.einsum('...a->a...', eps_a) eps_N_n1 = eps_a_[0, ...] eps_T_a_n1 = np.einsum('a...->...a', eps_a_[1:, ...]) sig_N = self.get_normal_law(eps_N_n1, **Eps) sig_T_a = self.get_tangential_law(eps_T_a_n1, **Eps) D_ = np.zeros(eps_a.shape + (eps_a.shape[-1], )) D_[..., 0, 0] = self.E_N # * (1 - omega_N) D_[..., 1, 1] = self.E_T # * (1 - omega_T) D_[..., 2, 2] = self.E_T # * (1 - omega_T) D_[..., 3, 3] = self.E_T # * (1 - omega_T) sig_a = np.concatenate([sig_N[..., np.newaxis], sig_T_a], axis=-1) return sig_a, D_ def get_eps_NT_p(self, **Eps): """Plastic strain tensor """ return Eps['eps_N_p'], Eps['eps_T_p_a'] def plot_idx(self, ax_sig, ax_d_sig, idx=0): eps_max = self.eps_max n_eps = self.n_eps eps1_range = np.linspace(1e-9, eps_max, n_eps) Eps = { var: np.zeros((1, ) + shape) for var, shape in self.state_var_shapes.items() } eps_range = np.zeros((n_eps, 4)) eps_range[:, idx] = eps1_range # monotonic load in the normal direction sig1_range, d_sig11_range = [], [] for eps_a in eps_range: sig_a, D_range = self.get_corr_pred(eps_a[np.newaxis, ...], 1, **Eps) sig1_range.append(sig_a[0, idx]) d_sig11_range.append(D_range[0, idx, idx]) sig1_range = np.array(sig1_range, dtype=np.float_) eps1_range = eps1_range[:len(sig1_range)] ax_sig.plot(eps1_range, sig1_range, color='blue') d_sig11_range = np.array(d_sig11_range, dtype=np.float_) ax_d_sig.plot(eps1_range, d_sig11_range, linestyle='dashed', color='gray') ax_sig.set_xlabel(r'$\varepsilon_{11}$ [-]') ax_sig.set_ylabel(r'$\sigma_{11}$ [MPa]') ax_d_sig.set_ylabel( r'$\mathrm{d} \sigma_{11} / \mathrm{d} \varepsilon_{11}$ [MPa]') ax_d_sig.plot(eps1_range[:-1], (sig1_range[:-1] - sig1_range[1:]) / (eps1_range[:-1] - eps1_range[1:]), color='orange', linestyle='dashed') def subplots(self, fig): ax_sig_N, ax_sig_T = fig.subplots(1, 2) ax_d_sig_N = ax_sig_N.twinx() ax_d_sig_T = ax_sig_T.twinx() return ax_sig_N, ax_d_sig_N, ax_sig_T, ax_d_sig_T def update_plot(self, axes): ax_sig_N, ax_d_sig_N, ax_sig_T, ax_d_sig_T = axes self.plot_idx(ax_sig_N, ax_d_sig_N, 0) self.plot_idx(ax_sig_T, ax_d_sig_T, 1)
class VDNTIM(MATS3DEval): """ Vectorized uncoupled normal tngential interface model """ # ------------------------------------------------------------------------- # Elasticity # ------------------------------------------------------------------------- name = 'equiv damage NTIM' E_N = Float(10000, MAT=True) E_T = Float(1000, MAT=True) epsilon_0 = Float(59.0e-6, MAT=True, label="a", desc="Lateral pressure coefficient", enter_set=True, auto_set=False) epsilon_f = Float(250.0e-6, MAT=True, label="a", desc="Lateral pressure coefficient", enter_set=True, auto_set=False) c_T = Float(0.01, MAT=True, label="a", desc="Lateral pressure coefficient", enter_set=True, auto_set=False) ipw_view = View( Item('E_T', readonly=True), Item('E_N', readonly=True), Item('epsilon_0'), Item('epsilon_f'), Item('c_T'), Item('eps_max'), Item('n_eps') ) n_D = 3 state_var_shapes = dict( kappa=(), omega_N=(), omega_T=() ) def get_e_equiv(self, eps_N, eps_T): r""" Returns a list of the microplane equivalent strains based on the list of microplane strain vectors """ # positive part of the normal strain magnitude for each microplane e_N_pos = (np.abs(eps_N) + eps_N) / 2.0 # tangent strain ratio c_T = self.c_T # equivalent strain for each microplane eps_equiv = np.sqrt(e_N_pos * e_N_pos + c_T * eps_T) return eps_equiv def get_normal_law(self, eps_N, eps_T_a, kappa, omega_N, omega_T): E_N = self.E_N E_T = self.E_T eps_T = np.sqrt(np.einsum('...a,...a->...', eps_T_a, eps_T_a)) eps_equiv = self.get_e_equiv(eps_N, eps_T) kappa[...] = np.max(np.array([kappa, eps_equiv]), axis=0) epsilon_0 = self.epsilon_0 epsilon_f = self.epsilon_f I = np.where(kappa >= epsilon_0) omega_N[I] = ( 1.0 - (epsilon_0 / kappa[I] * np.exp(-1.0 * (kappa[I] - epsilon_0) / (epsilon_f - epsilon_0)) ) ) sig_N = (1 - omega_N) * E_N * eps_N sig_T_a = (1 - omega_N[..., np.newaxis]) * E_T * eps_T_a sig_a = np.concatenate([sig_N[...,np.newaxis], sig_T_a], axis=-1) return sig_a def get_corr_pred(self, eps_a, t_n1, **Eps): eps_a_ = np.einsum('...a->a...',eps_a) eps_N = eps_a_[0,...] eps_T_a = np.einsum('a...->...a', eps_a_[1:,...]) sig_a = self.get_normal_law(eps_N, eps_T_a, **Eps) D_ = np.zeros(eps_a.shape + (eps_a.shape[-1],)) D_[..., 0, 0] = self.E_N# * (1 - omega_N) D_[..., 1, 1] = self.E_T# * (1 - omega_T) D_[..., 2, 2] = self.E_T# * (1 - omega_T) D_[..., 3, 3] = self.E_T# * (1 - omega_T) return sig_a, D_ def get_eps_NT_p(self, **Eps): """Plastic strain tensor """ return None def plot_idx(self, ax_sig, ax_d_sig, idx=0): eps_max = self.eps_max n_eps = self.n_eps eps1_range = np.linspace(1e-9,eps_max,n_eps) Eps = { var : np.zeros( (1,) + shape ) for var, shape in self.state_var_shapes.items() } eps_range = np.zeros((n_eps, 4)) eps_range[:,idx] = eps1_range # monotonic load in the normal direction sig1_range, d_sig11_range = [], [] for eps_a in eps_range: sig_a, D_range = self.get_corr_pred(eps_a[np.newaxis, ...], 1, **Eps) sig1_range.append(sig_a[0, idx]) d_sig11_range.append(D_range[0, idx, idx]) sig1_range = np.array(sig1_range, dtype=np.float_) eps1_range = eps1_range[:len(sig1_range)] ax_sig.plot(eps1_range, sig1_range,color='blue') d_sig11_range = np.array(d_sig11_range, dtype=np.float_) ax_d_sig.plot(eps1_range, d_sig11_range, linestyle='dashed', color='gray') ax_sig.set_xlabel(r'$\varepsilon_{11}$ [-]') ax_sig.set_ylabel(r'$\sigma_{11}$ [MPa]') ax_d_sig.set_ylabel(r'$\mathrm{d} \sigma_{11} / \mathrm{d} \varepsilon_{11}$ [MPa]') ax_d_sig.plot(eps1_range[:-1], (sig1_range[:-1]-sig1_range[1:])/(eps1_range[:-1]-eps1_range[1:]), color='orange', linestyle='dashed') def subplots(self, fig): ax_sig_N, ax_sig_T = fig.subplots(1,2) ax_d_sig_N = ax_sig_N.twinx() ax_d_sig_T = ax_sig_T.twinx() return ax_sig_N, ax_d_sig_N, ax_sig_T, ax_d_sig_T def update_plot(self, axes): ax_sig_N, ax_d_sig_N, ax_sig_T, ax_d_sig_T = axes self.plot_idx(ax_sig_N, ax_d_sig_N, 0) self.plot_idx(ax_sig_T, ax_d_sig_T, 1)
class MATS2DScalarDamage(MATS2DEval): r'''Isotropic damage model. ''' name = 'isotropic damage model' node_name = 'isotropic damage model' tree = ['omega_fn','strain_norm'] omega_fn = EitherType( options=[('exp-slope', ExpSlopeDamageFn), ('linear', LinearDamageFn), ('abaqus', AbaqusDamageFn), ('fracture-energy', GfDamageFn), ('weibull-CDF', WeibullDamageFn), ], MAT=True, on_option_change='link_omega_to_mats' ) D_alg = Float(0) r'''Selector of the stiffness calculation. ''' eps_max = Float(0.03, ALG=True) # upon change of the type attribute set the link to the material model def link_omega_to_mats(self): self.omega_fn_.trait_set(mats=self, E_name='E', x_max_name='eps_max') #========================================================================= # Material model #========================================================================= strain_norm = EitherType( options=[('Rankine', SN2DRankine), ('Masars', SN2DMasars), ('Energy', SN2DEnergy)], on_option_change='link_strain_norm_to_mats' ) def link_strain_norm_to_mats(self): self.strain_norm_.trait_set(mats=self) state_var_shapes = {'kappa': (), 'omega': ()} r''' Shapes of the state variables to be stored in the global array at the level of the domain. ''' def get_corr_pred(self, eps_ab_n1, tn1, kappa, omega): r''' Corrector predictor computation. @param eps_app_eng input variable - engineering strain ''' eps_eq = self.strain_norm_.get_eps_eq(eps_ab_n1, kappa) I = self.omega_fn_.get_f_trial(eps_eq, kappa) eps_eq_I = eps_eq[I] kappa[I] = eps_eq_I omega[I] = self._omega(eps_eq_I) phi = (1.0 - omega) D_abcd = np.einsum( '...,abcd->...abcd', phi, self.D_abcd ) sig_ab = np.einsum( '...abcd,...cd->...ab', D_abcd, eps_ab_n1 ) if self.D_alg > 0: domega_ds_I = self._omega_derivative(eps_eq_I) deps_eq_I = self.strain_norm_.get_deps_eq(eps_ab_n1[I]) D_red_I = np.einsum('...,...ef,cdef,...ef->...cdef', domega_ds_I, deps_eq_I, self.D_abcd, eps_ab_n1[I]) * self.D_alg D_abcd[I] -= D_red_I return sig_ab, D_abcd def _omega(self, kappa): return self.omega_fn_(kappa) def _omega_derivative(self, kappa): return self.omega_fn_.diff(kappa) ipw_view = View( Item('E'), Item('nu'), Item('strain_norm'), Item('omega_fn'), Item('stress_state'), Item('D_alg', latex=r'\theta_\mathrm{alg. stiff.}', editor=FloatRangeEditor(low=0,high=1)), Item('eps_max'), Item('G_f', latex=r'G_\mathrm{f}^{\mathrm{estimate}}', readonly=True), ) G_f = tr.Property(Float, depends_on='state_changed') @tr.cached_property def _get_G_f(self): eps_max = self.eps_max n_eps = 1000 eps11_range = np.linspace(1e-9,eps_max,n_eps) eps_range = np.zeros((len(eps11_range), 2, 2)) eps_range[:,1,1] = eps11_range state_vars = { var : np.zeros( (len(eps11_range),) + shape ) for var, shape in self.state_var_shapes.items() } sig_range, D = self.get_corr_pred(eps_range, 1, **state_vars) sig11_range = sig_range[:,1,1] return np.trapz(sig11_range, eps11_range) def subplots(self, fig): ax_sig = fig.subplots(1,1) ax_d_sig = ax_sig.twinx() return ax_sig, ax_d_sig def update_plot(self, axes): ax_sig, ax_d_sig = axes eps_max = self.eps_max n_eps = 100 eps11_range = np.linspace(1e-9,eps_max,n_eps) eps_range = np.zeros((n_eps, 2, 2)) eps_range[:,0,0] = eps11_range state_vars = { var : np.zeros( (n_eps,) + shape ) for var, shape in self.state_var_shapes.items() } sig_range, D_range = self.get_corr_pred(eps_range, 1, **state_vars) sig11_range = sig_range[:,0,0] ax_sig.plot(eps11_range, sig11_range,color='blue') d_sig1111_range = D_range[...,0,0,0,0] ax_d_sig.plot(eps11_range, d_sig1111_range, linestyle='dashed', color='gray') ax_sig.set_xlabel(r'$\varepsilon_{11}$ [-]') ax_sig.set_ylabel(r'$\sigma_{11}$ [MPa]') ax_d_sig.set_ylabel(r'$\mathrm{d} \sigma_{11} / \mathrm{d} \varepsilon_{11}$ [MPa]') ax_d_sig.plot(eps11_range[:-1], (sig11_range[:-1]-sig11_range[1:])/(eps11_range[:-1]-eps11_range[1:]), color='orange', linestyle='dashed') def get_omega(self, eps_ab, tn1, **Eps): return Eps['omega'] var_dict = tr.Property(tr.Dict(tr.Str, tr.Callable)) '''Dictionary of response variables ''' @tr.cached_property def _get_var_dict(self): var_dict = dict(omega=self.get_omega) var_dict.update(super()._get_var_dict()) return var_dict
class ConcreteMaterialModelAdv(ConcreteMatMod, bu.InjectSymbExpr): name = 'Concrete behavior' node_name = 'material model' symb_class = ConcreteMaterialModelAdvExpr d_a = Float(16, MAT=True) ## dia of steel mm E_c = Float(28000, MAT=True) ## tensile strength of Concrete in MPa f_t = Float(3, MAT=True) ## Fracture Energy in N/m c_1 = Float(3, MAT=True) c_2 = Float(6.93, MAT=True) f_c = Float(33.3, MAT=True) L_fps = Float(50, MAT=True) a = Float(1.038, MAT=True) b = Float(0.245, MAT=True) interlock_factor = Float(1, MAT=True) ipw_view = View( Item('d_a', latex=r'd_a'), Item('E_c', latex=r'E_c'), Item('f_t', latex=r'f_t'), Item('f_c', latex=r'f_c'), Item('c_1', latex=r'c_1'), Item('c_2', latex=r'c_2'), Item('L_fps', latex=r'L_{fps}'), Item('a', latex=r'a'), Item('b', latex=r'b'), Item('interlock_factor', latex=r'\gamma_\mathrm{ag}', editor=FloatRangeEditor(low=0, high=1)), Item('w_cr', latex=r'w_\mathrm{cr}', readonly=True), Item('L_c', latex=r'L_\mathrm{c}', readonly=True), Item('G_f', latex=r'G_\mathrm{f}', readonly=True)) # G_f_baz = tr.Property(depends_on='_ITR, _INC, _GEO, _MAT, _DSC') # @tr.cached_property # def _get_G_f_baz(self): # xi = self.sz_crack_tip_orientation. # return self.symb.G_f_baz L_c = tr.Property(Float, depends_on='state_changed') @tr.cached_property def _get_L_c(self): return self.E_c * self.G_f / self.f_t**2 w_cr = tr.Property(Float, depends_on='state_changed') @tr.cached_property def _get_w_cr(self): return (self.f_t / self.E_c) * self._get_L_c() G_f = tr.Property(Float, depends_on='state_changed') @tr.cached_property def _get_G_f(self): '''Calculating fracture energy ''' return (0.028 * self.f_c**0.18 * self.d_a**0.32) def get_w_tc(self): '''Calculating point of softening curve resulting in 0 stress ''' return (5.14 * self.G_f_baz / self.f_t) def get_sig_a(self, u_a): #w, s '''Calculating stresses ''' sig_w = self.get_sig_w(u_a[..., 0], u_a[..., 1]) tau_s = self.get_tau_s(u_a[..., 0], u_a[..., 1]) return np.einsum('b...->...b', np.array([sig_w, tau_s], dtype=np.float_)) #, tau_s def get_sig_w(self, w, s): return self.symb.get_sig_w(w, s) def get_tau_s(self, w, s): return self.symb.get_tau_s(w, s) * self.interlock_factor w_min_factor = Float(1.2) w_max_factor = Float(15) def plot3d_sig_w(self, ax3d, vot=1.0): w_min = -(self.f_c / self.E_c * self._get_L_c()) * self.w_min_factor w_max = self.w_cr * self.w_max_factor w_range = np.linspace(w_min, w_max, 100) s_max = 3 s_data = np.linspace(-1.1 * s_max, 1.1 * s_max, 100) s_, w_ = np.meshgrid(s_data, w_range) sig_w = self.get_sig_w(w_, s_) ax3d.plot_surface(w_, s_, sig_w, cmap='viridis', edgecolor='none') ax3d.set_xlabel(r'$w\;\;\mathrm{[mm]}$', fontsize=12) ax3d.set_ylabel(r'$s\;\;\mathrm{[mm]}$', fontsize=12) ax3d.set_zlabel(r'$\sigma_w\;\;\mathrm{[MPa]}$', fontsize=12) ax3d.set_title('crack opening law', fontsize=12) # def plot_sig_w(self, ax, vot=1.0): # w_min = -(self.f_c / self.E_c * self._get_L_c()) * self.w_min_factor # w_max = self.w_cr * self.w_max_factor # w_range = np.linspace(w_min, w_max, 100) # s_max = 3 # s_data = np.linspace(-1.1 * s_max, 1.1 * s_max, 100) # sig_w = self.get_sig_w(w_range, s_data) # ax.plot(w_range, sig_w, lw=2, color='red') # ax.fill_between(w_range, sig_w, # color='red', alpha=0.2) # ax.set_xlabel(r'$w\;\;\mathrm{[mm]}$', fontsize=12) # ax.set_ylabel(r'$\sigma\;\;\mathrm{[MPa]}$', fontsize=12) # ax.set_title('crack opening law', fontsize=12) def plot3d_tau_s(self, ax3d, vot=1.0): w_min = 1e-9 #-1 w_max = 3 w_data = np.linspace(w_min, w_max, 100) s_max = 3 s_data = np.linspace(-1.1 * s_max, 1.1 * s_max, 100) #-1.1 s_, w_ = np.meshgrid(s_data, w_data) tau_s = self.get_tau_s(w_, s_) ax3d.plot_surface(w_, s_, tau_s, cmap='viridis', edgecolor='none') ax3d.set_xlabel(r'$w\;\;\mathrm{[mm]}$', fontsize=12) ax3d.set_ylabel(r'$s\;\;\mathrm{[mm]}$', fontsize=12) ax3d.set_zlabel(r'$\tau\;\;\mathrm{[MPa]}$', fontsize=12) ax3d.set_title('aggregate interlock law', fontsize=12) # def subplots(self, fig): # #ax_2d = fig.add_subplot(1, 2, 2) # ax_3d_1 = fig.add_subplot(1, 2, 2, projection='3d') # ax_3d_2 = fig.add_subplot(1, 2, 1, projection='3d') # return ax_3d_1, ax_3d_2 # # def update_plot(self, axes): # '''Plotting function # ''' # ax_3d_1, ax_3d_2 = axes # self.plot3d_sig_w(ax_3d_1) # self.plot3d_tau_s(ax_3d_2) def get_eps_plot_range(self): return np.linspace(1.1 * self.eps_cu, 1.1 * self.eps_tu, 300) def get_sig(self, eps): return self.factor * self.symb.get_sig_w(eps)
class TShape(CrossSectionShapeBase): B_f = Float(400, GEO=True) B_w = Float(100, GEO=True) H_w = Float(300, GEO=True) ipw_view = View( *CrossSectionShapeBase.ipw_view.content, Item('B_f', minmax=(10, 3000), latex=r'B_f \mathrm{[mm]}'), Item('B_w', minmax=(10, 3000), latex=r'B_w \mathrm{[mm]}'), Item('H_w', minmax=(10, 3000), latex=r'H_w \mathrm{[mm]}'), ) def get_cs_area(self): return self.B_w * self.H_w + self.B_f * (self.H - self.H_w) def get_cs_i(self): A_f = self.B_f * (self.H - self.H_w) Y_f = (self.H - self.H_w) / 2 + self.H_w I_f = self.B_f * (self.H - self.H_w)**3 / 12 A_w = self.B_w * self.H_w Y_w = self.H_w / 2 I_w = self.B_w * self.H_w**3 / 12 Y_c = (Y_f * A_f + Y_w * A_w) / (A_f + A_w) I_c = I_f + A_f * (Y_c - Y_f)**2 + I_w + A_w * (Y_c - Y_w)**2 return Y_c, I_c get_b = tr.Property(tr.Callable, depends_on='+input') @tr.cached_property def _get_get_b(self): z_ = sp.Symbol('z') b_p = sp.Piecewise((self.B_w, z_ < self.H_w), (self.B_f, True)) return sp.lambdify(z_, b_p, 'numpy') def update_plot(self, ax): # Start drawing from bottom center of the cross section cs_points = np.array([[self.B_w / 2, 0], [self.B_w / 2, self.H_w], [self.B_f / 2, self.H_w], [self.B_f / 2, self.H], [-self.B_f / 2, self.H], [-self.B_f / 2, self.H_w], [-self.B_w / 2, self.H_w], [-self.B_w / 2, 0]]) cs = Polygon(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, TShape.get_cs_i(self)[0], color='white', s=self.B_w, marker="+") ax.autoscale_view() ax.set_aspect('equal') ax.annotate('Area = {} $mm^2$'.format(int(TShape.get_cs_area(self)), 0), xy=(-self.H / 2 * 0.8, (self.H / 2 + self.H_w / 2)), color='blue') ax.annotate('I = {} $mm^4$'.format(int(TShape.get_cs_i(self)[1]), 0), xy=(-self.H / 2 * 0.8, (self.H / 2 + self.H_w / 2) * 0.9), color='blue') ax.annotate('$Y_c$', xy=(0, TShape.get_cs_i(self)[0] * 0.85), color='purple')