class MonotonicLoadingScenario(LoadingScenario): n_incr = Int(10, BC=True) maximum_loading = Float(1.0, BC=True, enter_set=True, auto_set=False, symbol='\phi_{\max}', desc='load factor at maximum load level', unit='-') ipw_view = bu.View( bu.Item("n_incr"), bu.Item('maximum_loading'), ) xy_arrays = Property(depends_on="state_changed") @cached_property def _get_xy_arrays(self): t_arr = np.linspace(0, self.t_max, self.n_incr) d_arr = np.linspace(0, self.maximum_loading, self.n_incr) return t_arr, d_arr def write_figure(self, f, rdir, rel_study_path): print('FNAME', self.node_name) fname = 'fig_' + self.node_name.replace(' ', '_') + '.pdf' print('FNAME', fname) self._update_xy_arrays() f.write(r''' \multicolumn{3}{r}{\includegraphics[width=5cm]{%s}}\\ ''' % join(rel_study_path, fname)) self.savefig(join(rdir, fname))
class PO_ELF_RLM(bu.Model, bu.InjectSymbExpr): """ Pullout elastic long fiber and rigid long matrix """ symb_class = PO_ELF_RLM_Symb name = "PO-ELF-RLM" E_f = bu.Float(210000, MAT=True) tau = bu.Float(8, MAT=True) A_f = bu.Float(100, CS=True) p = bu.Float(20, CS=True) L_b = bu.Float(300, GEO=True) w_max = bu.Float(3, BC=True) ipw_view = bu.View(bu.Item('E_f', latex=r'E_\mathrm{f}~[\mathrm{MPa}]'), bu.Item('tau', latex=r'\tau~[\mathrm{MPa}]'), bu.Item('A_f', latex=r'A_\mathrm{f}~[\mathrm{mm}^2]'), bu.Item('p', latex=r'p~[\mathrm{mm}]'), bu.Item('L_b', latex=r'L_\mathrm{b}~[\mathrm{mm}]'), bu.Item('w_max', latex=r'w_\max~[\mathrm{mm}]')) w_range = tr.Property(depends_on='state_changed') """Pull-out range w""" @tr.cached_property def _get_w_range(self): return np.linspace(0, self.w_max, 100) def update_plot(self, ax): P_range = self.symb.get_Pw_pull(self.w_range) ax.plot(self.w_range, P_range * 0.001) ax.set_ylabel(r'$P$ [kN]') ax.set_xlabel(r'$w$ [mm]')
class EC2ConcreteMatMod(EC2ConcreteMatModBase, bu.InjectSymbExpr): name = 'EC2 Concrete' symb_class = EC2ConcreteMatModSymbExpr # Required attributes f_cm = bu.Float(28) f_ck = tr.Property(desc='Characteristic compressive strength of concrete', MAT=True) def _get_f_ck(self): return EC2.get_f_ck_from_f_cm(self.f_cm) eps_cy = tr.Property(desc='Matrix compressive yield strain', MAT=True) def _get_eps_cy(self): return -EC2.get_eps_c1(self.f_ck) eps_cu = tr.Property(desc='Ultimate matrix compressive strain', MAT=True) def _get_eps_cu(self): return -EC2.get_eps_cu1(self.f_ck) ipw_view = bu.View(*EC2ConcreteMatModBase.ipw_view.content, ) def get_sig(self, eps): return self.factor * self.symb.get_sig(eps)
class SteelReinfMatMod(ReinfMatMod, bu.InjectSymbExpr): name = 'Steel' symb_class = SteelReinfMatModSymbExpr E_s = bu.Float(200000, MAT=True, desc='E modulus of steel') f_sy = bu.Float(500, MAT=True, desc='steel yield stress') eps_ud = bu.Float(0.025, MAT=True, desc='steel failure strain') eps_sy = tr.Property(bu.Float, depends_on='+MAT') @tr.cached_property def _get_eps_sy(self): return self.f_sy / self.E_s ipw_view = bu.View( bu.Item('factor'), bu.Item('E_s', latex=r'E_\mathrm{s} \mathrm{[N/mm^{2}]}'), bu.Item('f_sy', latex=r'f_\mathrm{sy} \mathrm{[N/mm^{2}]}'), bu.Item('eps_ud', latex=r'\varepsilon_\mathrm{ud} \mathrm{[-]}'), bu.Item('eps_sy', latex=r'\varepsilon_\mathrm{sy} \mathrm{[-]}', readonly=True), ) def get_eps_plot_range(self): return np.linspace(-1.1 * self.eps_ud, 1.1 * self.eps_ud, 300) def get_sig(self, eps): temp = self.f_sy self.f_sy *= self.factor sig = self.symb.get_sig(eps) self.f_sy = temp return sig
class BMCSNodeBase(bu.Model): node_name = Str('<unnamed>') ipw_view = bu.View() def update_plot(self, axes): pass tree_view = View() ui = WeakRef def set_traits_with_metadata(self, value, **metadata): traits_names = self.trait_names(**metadata) for tname in traits_names: setattr(self, tname, value) parent = WeakRef root = Property '''Root node of tree node hierarchy ''' def _get_root(self): if self.parent: return self.parent.root return self
class CarbonReinfMatMod(ReinfMatMod, bu.InjectSymbExpr): name = 'Carbon' symb_class = CarbonReinfMatModSymbExpr E = bu.Float(200000, MAT=True, desc='E modulus of carbon') f_t = bu.Float(2000, MAT=True, desc='carbon breaking stress') eps_cr = tr.Property(bu.Float, depends_on='+MAT') @tr.cached_property def _get_eps_cr(self): return self.f_t / self.E ipw_view = bu.View( bu.Item('factor'), bu.Item('E', latex=r'E \mathrm{[N/mm^{2}]}'), bu.Item('f_t', latex=r'f_\mathrm{t} \mathrm{[N/mm^{2}]}'), bu.Item('eps_cr', latex=r'\varepsilon_\mathrm{cr} \mathrm{[-]}'), ) def get_eps_plot_range(self): return np.linspace(-0.1 * self.eps_cr, 1.1 * self.eps_cr, 300) def get_sig(self, eps): # TODO: factor should be applied only to strength in case of steel/carbon according to EC2 temp = self.f_t self.f_t *= self.factor sig = self.symb.get_sig(eps) self.f_t = temp return sig
class AbaqusDamageFn(DamageFnInjectSymbExpr): name = 'Abaqus damage function' symb_class = AbaqusDamageFnSymbExpr latex_eq = Str(r'''Damage function (Abaqus) \begin{align} \omega = g(\kappa) = 1 -\left(\frac{s_0}{\kappa}\right)\left[ 1 - \frac{1 - \exp(- \alpha(\frac{\kappa - s_0}{s_u - s_0})}{1 - \exp(-\alpha)} \right] \end{align} where $\kappa$ is the state variable representing the maximum slip that occurred so far in in the history of loading. ''') kappa_u = bu.Float( 0.003, MAT=True, symbol="kappa_u", unit='mm', desc="parameter of the damage function", ) alpha = bu.Float(0.1, MAT=True, symbol=r"\alpha", desc="parameter controlling the slope of damage", unit='-') ipw_view = bu.View( bu.Item('kappa_0', latex=r'\kappa_0'), bu.Item('kappa_u', latex=r'\kappa_\mathrm{u}'), bu.Item('alpha'), )
class CrossSection(BMCSLeafNode, RInputRecord): '''Parameters of the pull-out cross section ''' node_name = 'cross-section' A_m = bu.Float(15240, CS=True, input=True, unit=r'$\mathrm{mm}^2$', symbol=r'A_\mathrm{m}', auto_set=False, enter_set=True, desc='matrix area') A_f = bu.Float(153.9, CS=True, input=True, unit='$\\mathrm{mm}^2$', symbol='A_\mathrm{f}', auto_set=False, enter_set=True, desc='reinforcement area') P_b = bu.Float(44, CS=True, input=True, unit='$\\mathrm{mm}$', symbol='p_\mathrm{b}', auto_set=False, enter_set=True, desc='perimeter of the bond interface') view = View(Item('A_m'), Item('A_f'), Item('P_b')) tree_view = view ipw_view = bu.View(bu.Item('A_m'), bu.Item('A_f'), bu.Item('P_b'))
class WeibullDamageFn(DamageFnInjectSymbExpr): name = 'Linear damage function' symb_class = WeibullDamageFnSymbExpr kappa_0 = bu.Float(1e-5) # the inelastic regime starts right from the beginning lambda_ = bu.Float( 0.03, MAT=True, symbol="lambda_", unit='mm', desc="Weibull scale parameter", ) m = bu.Float( 5, MAT=True, symbol="m", desc="Weibull shape parameter", ) ipw_view = bu.View( bu.Item('lambda_', latex=r'\lambda'), bu.Item('m', latex=r'm'), )
class MultilinearDamageFn(DamageFn): name = 'Multilinear damage function' s_data = bu.Str( '1', tooltip='Comma-separated list of strain values', MAT=True, unit='mm', symbol='s', desc='slip values', ) omega_data = bu.Str( '1', tooltip='Comma-separated list of damage values', MAT=True, unit='-', symbol=r'\omega', desc='shear stress values', ) s_omega_table = Property def _set_s_omega_table(self, data): s_data, omega_data = data if len(s_data) != len(omega_data): raise ValueError('s array and tau array must have the same size') self.damage_law.set(xdata=s_data, ydata=omega_data) @observe('state_changed') def _update_damage_law(self, event=None): s_data = np.fromstring(self.s_data, dtype=np.float_, sep=',') omega_data = np.fromstring(self.omega_data, dtype=np.float_, sep=',') s_data = np.hstack([[0, self.s_0], s_data]) omega_data = np.hstack([[0, 0], omega_data]) n = np.min([len(s_data), len(omega_data)]) self.damage_law.set(xdata=s_data[:n], ydata=omega_data[:n]) damage_law = Instance(MFnLineArray) def _damage_law_default(self): return MFnLineArray(xdata=[0.0, 1.0], ydata=[0.0, 0.0], plot_diff=False) def __call__(self, kappa): shape = kappa.shape return self.damage_law(kappa.flatten()).reshape(*shape) def diff(self, kappa): shape = kappa.shape return self.damage_law.diff(kappa.flatten()).reshape(*shape) ipw_view = bu.View( bu.Item('s_0'), bu.Item('s_data'), bu.Item('omega_data'), )
class Simulator(BMCSTreeNode, TLineMixIn): r'''Base class for simulators included in the BMCS Tool Suite. It implements the state dependencies within the simulation tree. It handles also the communication between the simulation and the user interface in several modes of interaction. ''' name = 'simulator' @tr.observe('state_changed') def _model_structure_changed(self, event=None): self.tloop.restart = True #========================================================================= # TIME LOOP #========================================================================= tloop = Property(Instance(ITLoop), depends_on='state_changed') r'''Time loop constructed based on the current model. ''' @cached_property def _get_tloop(self): return self.tstep.tloop_type(tstep=self.tstep, tline=self.tline) def __init__(self, tstep, *args, **kw): self.tstep = tstep super(Simulator, self).__init__(*args, **kw) tstep = tr.WeakRef(ITStep) hist = tr.Property def _get_hist(self): return self.tstep.hist def run(self): r'''Run a thread if it does not exist - do nothing otherwise ''' self.tloop() return interrupt = tr.DelegatesTo('tloop') def reset(self): self.tloop.reset() ipw_view = bu.View( run_method='tloop', reset_method='reset', interrupt_var='interrupt', time_var='t', time_max='t_max', )
class PWLConcreteMatMod(ConcreteMatMod, bu.InjectSymbExpr): name = 'Concrete PWL' symb_class = PWLConcreteMatModSymbExpr E_ct = bu.Float(24000, MAT=True, desc='E modulus of matrix on tension') E_cc = bu.Float(25000, MAT=True, desc='E modulus of matrix on compression') eps_cr = bu.Float(0.001, MAT=True, desc='Matrix cracking strain') _eps_cy = bu.Float(-0.003, MAT=True) _eps_cu = bu.Float(-0.01, MAT=True) # Enforcing negative values for eps_cu and eps_cy eps_cy = tr.Property(desc='Matrix compressive yield strain') def _set_eps_cy(self, value): self._eps_cy = value def _get_eps_cy(self): return -np.fabs(self._eps_cy) eps_cu = tr.Property(desc='Ultimate matrix compressive strain') def _set_eps_cu(self, value): self._eps_cu = value def _get_eps_cu(self): return -np.fabs(self._eps_cu) eps_tu = bu.Float(0.003, MAT=True, desc='Ultimate matrix tensile strain') mu = bu.Float( 0.33, MAT=True, desc= 'Post crack tensile strength ratio (represents how much strength is left after \ the crack because of short steel fibers in the mixture)' ) ipw_view = bu.View( bu.Item('factor'), bu.Item('E_ct', latex=r'E_\mathrm{ct} \mathrm{[N/mm^{2}]}'), bu.Item('E_cc', latex=r'E_\mathrm{cc} \mathrm{[N/mm^{2}]}'), bu.Item('eps_cr', latex=r'\varepsilon_{cr}'), bu.Item('eps_cy', latex=r'\varepsilon_{cy}', editor=bu.FloatEditor()), bu.Item('eps_cu', latex=r'\varepsilon_{cu}', editor=bu.FloatEditor()), bu.Item('eps_tu', latex=r'\varepsilon_{tu}'), bu.Item('mu', latex=r'\mu')) 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(eps)
class LiDamageFn(DamageFn): name = 'Two parameter damage' latex_eq = Str(r'''Damage function (Li) \begin{align} \omega = g(\kappa) = \frac{\alpha_1}{1 + \exp(-\alpha_2 \kappa + 6 )} \end{align} where $\kappa$ is the state variable representing the maximum slip that occurred so far in in the history of loading. ''') alpha_1 = bu.Float( value=1, MAT=True, symbol=r'\alpha_1', unit='-', desc="parameter controlling the shape of the damage function") alpha_2 = bu.Float( 2000., MAT=True, symbol=r'\alpha_2', unit='-', desc="parameter controlling the shape of the damage function") def __call__(self, kappa): alpha_1 = self.alpha_1 alpha_2 = self.alpha_2 s_0 = self.s_0 omega = np.zeros_like(kappa, dtype=np.float_) d_idx = np.where(kappa >= s_0)[0] k = kappa[d_idx] omega[d_idx] = 1. / \ (1. + np.exp(-1. * alpha_2 * (k - s_0) + 6.)) * alpha_1 return omega def diff(self, kappa): alpha_1 = self.alpha_1 alpha_2 = self.alpha_2 s_0 = self.s_0 return ((alpha_1 * alpha_2 * np.exp(-1. * alpha_2 * (kappa - s_0) + 6.)) / (1 + np.exp(-1. * alpha_2 * (kappa - s_0) + 6.))**2) ipw_view = bu.View( bu.Item('s_0'), bu.Item('alpha_1', editor=bu.FloatRangeEditor(low=0, high=1)), bu.Item('alpha_2'), )
class WBNumTessellationInvest(WBNumTessellationBase): """ A class to investigate the angles for tessellating three cells manually. """ name = 'WBNumTessellationInvest' rot_br = bu.Float(0.5) rot_ur = bu.Float(0.5) investigate_rot = bu.Bool ipw_view = bu.View( *WBNumTessellationBase.ipw_view.content, bu.Item('investigate_rot'), bu.Item('rot_br', latex=r'rot~br', editor=bu.FloatRangeEditor(low=0, high=2 * np.pi, n_steps=150, continuous_update=True)), bu.Item('rot_ur', latex=r'rot~ur', editor=bu.FloatRangeEditor(low=0, high=2 * np.pi, n_steps=150, continuous_update=True)), ) def setup_plot(self, pb): super().setup_plot(pb) def update_plot(self, pb): if self.k3d_mesh: sol = self.sol X_Ia = self.X_Ia.astype(np.float32) br_X_Ia = self._get_br_X_Ia( self.X_Ia, self.rot_br if self.investigate_rot else sol[0]).astype( np.float32) ur_X_Ia = self._get_ur_X_Ia( self.X_Ia, self.rot_ur if self.investigate_rot else sol[1]).astype( np.float32) self.k3d_mesh['X_Ia'].vertices = X_Ia self.k3d_mesh['br_X_Ia'].vertices = br_X_Ia self.k3d_mesh['ur_X_Ia'].vertices = ur_X_Ia self.k3d_wireframe['X_Ia'].vertices = X_Ia self.k3d_wireframe['br_X_Ia'].vertices = br_X_Ia self.k3d_wireframe['ur_X_Ia'].vertices = ur_X_Ia else: self.setup_plot(pb)
class Geometry(BMCSLeafNode, RInputRecord): node_name = 'geometry' L_x = bu.Float(45, GEO=True, input=True, unit='$\mathrm{mm}$', symbol='L', auto_set=False, enter_set=True, desc='embedded length') view = View(Item('L_x'), ) tree_view = view ipw_view = bu.View(bu.Item('L_x'), )
class ExpSlopeDamageFn(DamageFnInjectSymbExpr): name = 'Exponential with slope' symb_class = ExpSlopeDamageFnSymbExpr kappa_f = bu.Float( 0.001, MAT=True, symbol=r'\kappa_\mathrm{f}', unit='mm/mm', desc="derivative of the damage function at the onset of damage") ipw_view = bu.View( bu.Item('kappa_0', latex=r'\kappa_0'), bu.Item('kappa_f', latex=r'\kappa_\mathrm{f}'), )
class EC2PlateauConcreteMatMod(EC2ConcreteMatModBase, bu.InjectSymbExpr): name = 'EC2 Concrete with Plateau' symb_class = EC2PlateauConcreteMatModSymbExpr f_cm = bu.Float(28) f_cd = tr.Property(desc='Design compressive strength of concrete', MAT=True) def _get_f_cd(self): if self.factor == 1: return self.f_cm else: return EC2.get_f_cd(self.f_ck, factor=self.factor) f_ck = tr.Property(desc='Characteristic compressive strength of concrete', MAT=True) def _get_f_ck(self): return EC2.get_f_ck_from_f_cm(self.f_cm) n = tr.Property(desc='Exponent used in EC2, eq. 3.17', MAT=True) def _get_n(self): return EC2.get_n(self.f_ck) eps_cy = tr.Property(desc='Matrix compressive yield strain', MAT=True) def _get_eps_cy(self): return -EC2.get_eps_c2(self.f_ck) eps_cu = tr.Property(desc='Ultimate matrix compressive strain', MAT=True) def _get_eps_cu(self): return -EC2.get_eps_cu2(self.f_ck) def get_sig(self, eps): sig = self.symb.get_sig(eps) # Compression branch is scaled when defining f_cd sig_with_scaled_tension_branch = np.where(sig > 0, self.factor * sig, sig) return sig_with_scaled_tension_branch ipw_view = bu.View(*EC2ConcreteMatModBase.ipw_view.content, )
class LinearDamageFn(DamageFnInjectSymbExpr): name = 'Linear damage function' symb_class = LinearDamageFnSymbExpr kappa_0 = 0.01 kappa_u = bu.Float( 0.03, MAT=True, symbol="kappa_u", unit='mm', desc="parameter of the damage function", ) ipw_view = bu.View( bu.Item('kappa_0', latex=r'\kappa_0'), bu.Item('kappa_u', latex=r'\kappa_\mathrm{u}'), )
class GfDamageFn(DamageFnInjectSymbExpr): name = 'self-regularized' symb_class = GfDamageFnSymbExpr G_f = bu.Float( 0.1, MAT=True, symbol="G_\mathrm{f}", unit='N/mm', desc="derivative of the damage function at the onset of damage") min_G_f = Property(bu.Float, depends_on='state_changed') def _get_min_G_f(self): return self.E * self.kappa_0**2 / 2 E_ = bu.Float(10000.0, MAT=True, label="E", desc="Young's modulus") E = Property(bu.Float) def _get_E(self): if self.mats: return getattr(self.mats, self.E_name) else: return self.E_ def __call__(self, kappa): return self.symb.get_omega_(kappa) def diff(self, kappa): return self.symb.get_d_omega_(kappa) ipw_view = bu.View( bu.Item('kappa_0', latex=r'\kappa_0 [\mathrm{mm}]'), bu.Item('G_f', latex=r'G_\mathrm{f} [\mathrm{N/mm}]'), bu.Item('E', latex=r'E [\mathrm{MPa}]', readonly=True), bu.Item('min_G_f', latex=r'\min(G_\mathrm{f})', readonly=True), bu.Item('plot_max', readonly=True), )
class TimeFunction(bu.InteractiveModel): name = 'Time function' n_i = bu.Int(0, input=True, desc='number of data points between data points') '''Number of steps between two values ''' values = tr.Array(np.float_, value=[0, 1]) '''Values of the time function. ''' ipw_view = bu.View(bu.Item('n_i', latex=r'n_i', minmax=(0, 100))) t_step = tr.Property(tr.Float, depends_on='+input') '''Step size. ''' @tr.cached_property def _get_t_step(self): n_values = len(self.values) return (self.n_i + 1) * n_values tf = tr.Property(depends_on='data_points') def _get_tf(self): n_values = len(self.values) t_values = np.linspace(0, 1, n_values) return interpolate.interp1d(t_values, self.values) def __call__(self, arg): return self.tf(arg) def update_plot(self, ax): n_values = len(self.values) t = np.linspace(0, 1, (self.n_i) * (n_values - 1) + n_values) v = self.tf(t) ax.plot(t, v, '-o')
class SlideExplorer(bu.Model): name = 'Explorer' tree = [ 'slide_model', 'inel_state_evolution', 'energy_dissipation', 'tf_s_x', 'tf_s_y', 'tf_w' ] slide_model = bu.Instance(Slide32, (), tree=True) # slide_model = bu.Instance(Slide34, (), tree=True) energy_dissipation = bu.Instance(EnergyDissipation, tree=True) '''Viewer to the energy dissipation''' def _energy_dissipation_default(self): return EnergyDissipation(slider_exp=self) inel_state_evolution = bu.Instance(InelStateEvolution, tree=True) '''Viewer to the inelastic state evolution''' def _inel_state_evolution_default(self): return InelStateEvolution(slider_exp=self) time_fn = bu.Instance(TimeFunction, (), tree=True) def __init__(self, *args, **kw): super(SlideExplorer, self).__init__(*args, **kw) self.reset_i() n_Eps = tr.Property() def _get_n_Eps(self): return len(self.slide_model.symb.Eps) s_x_1 = bu.Float(0, INC=True) s_y_1 = bu.Float(0, INC=True) w_1 = bu.Float(0, INC=True) tf_s_x = bu.Instance(TimeFunction, TIME=True) def _tf_s_x_default(self): return TFSelector() tf_s_y = bu.Instance(TimeFunction, TIME=True) def _tf_s_y_default(self): return TFSelector() tf_w = bu.Instance(TimeFunction, TIME=True) def _tf_w_default(self): return TFSelector() n_steps = bu.Int(10, ALG=True) k_max = bu.Int(20, ALG=True) Sig_arr = tr.Array Eps_arr = tr.Array Sig_t = tr.Property def _get_Sig_t(self): return self.Sig_arr Eps_t = tr.Property def _get_Eps_t(self): return self.Eps_arr ipw_view = bu.View(bu.Item('s_x_1', latex=r's_x'), bu.Item('s_y_1', latex=r's_y'), bu.Item('w_1', latex=r'w'), bu.Item('n_steps'), bu.Item('k_max'), bu.Item('t_max', readonly=True), time_editor=bu.ProgressEditor( run_method='run', reset_method='reset', interrupt_var='sim_stop', time_var='t', time_max='t_max', )) def reset_i(self): self.s_x_0, self.s_y_0, self.w_0 = 0, 0, 0 self.t0 = 0 self.t = 0 self.t_max = 1 self.Sig_arr = np.zeros((0, self.n_Eps)) self.Eps_arr = np.zeros((0, self.n_Eps)) self.Sig_record = [] self.Eps_record = [] self.iter_record = [] self.t_arr = [] self.s_x_t, self.s_y_t, self.w_t = [], [], [] self.Eps_n1 = np.zeros((self.n_Eps, ), dtype=np.float_) self.Sig_n1 = np.zeros((self.n_Eps, ), dtype=np.float_) self.s_x_1 = 0 self.s_y_1 = 0 self.w_1 = 0 t = bu.Float(0) t_max = bu.Float(1) def _t_max_changed(self): self.inel_state_evolution.t_max = self.t_max def get_response_i(self, update_progress=lambda t: t): # global Eps_record, Sig_record, iter_record # global t_arr, s_x_t, s_y_t, w_t, s_x_0, s_y_0, w_0, t0, Eps_n1 n_steps = self.n_steps t_i = np.linspace(0, 1, n_steps + 1) t1 = self.t0 + 1 self.t_max = t1 ti_arr = np.linspace(self.t0, t1, n_steps + 1) delta_t = t1 - self.t0 tf_s_x = self.tf_s_x(np.linspace(0, delta_t, n_steps + 1)) tf_s_y = self.tf_s_y(np.linspace(0, delta_t, n_steps + 1)) tf_w = self.tf_w(np.linspace(0, delta_t, n_steps + 1)) # si_x_t = tf_s_x * np.linspace(self.s_x_0, self.s_x_1, n_steps + 1) + 1e-9 # si_y_t = tf_s_y * np.linspace(self.s_y_0, self.s_y_1, n_steps + 1) + 1e-9 # wi_t = tf_w * np.linspace(self.w_0, self.w_1, n_steps + 1) + 1e-9 si_x_t = self.s_x_0 + tf_s_x * (self.s_x_1 - self.s_x_0) + 1e-9 si_y_t = self.s_y_0 + tf_s_y * (self.s_y_1 - self.s_y_0) + 1e-9 wi_t = self.w_0 + tf_w * (self.w_1 - self.w_0) + 1e-9 for i, (t, s_x_n1, s_y_n1, w_n1) in enumerate(zip(t_i, si_x_t, si_y_t, wi_t)): if self.slide_model.debug_level == 1: print('============= INCREMENT', i) try: self.Eps_n1, self.Sig_n1, k = self.slide_model.get_sig_n1( s_x_n1, s_y_n1, w_n1, self.Sig_n1, self.Eps_n1, self.k_max) except ConvergenceError as e: print(e) break self.Sig_record.append(self.Sig_n1) self.Eps_record.append(self.Eps_n1) self.iter_record.append(k) self.t = t self.Sig_arr = np.array(self.Sig_record, dtype=np.float_) self.Eps_arr = np.array(self.Eps_record, dtype=np.float_) self.iter_t = np.array(self.iter_record, dtype=np.int_) n_i = len(self.iter_t) self.t_arr = np.hstack([self.t_arr, ti_arr])[:n_i] self.s_x_t = np.hstack([self.s_x_t, si_x_t])[:n_i] self.s_y_t = np.hstack([self.s_y_t, si_y_t])[:n_i] self.w_t = np.hstack([self.w_t, wi_t])[:n_i] self.t0 = t1 self.s_x_0, self.s_y_0, self.w_0 = self.s_x_1, self.s_y_1, self.w_1 # set the last step index in the response browser self.inel_state_evolution.t_max = self.t_arr[-1] return # ## Plotting functions # To simplify postprocessing examples, here are two aggregate plotting functions, one for the state and force variables, the other one for the evaluation of energies def plot_sig_w(self, ax): sig_t = self.Sig_arr.T[2, ...] ax.plot(self.w_t, sig_t, color='orange', lw=3) def plot3d_Sig_Eps(self, ax3d): tau_x, tau_y = self.Sig_arr.T[:2, ...] tau = np.sqrt(tau_x**2 + tau_y**2) ax3d.plot3D(self.s_x_t, self.s_y_t, tau, color='orange', lw=2) def run(self, update_progress=lambda t: t): try: self.get_response_i(update_progress) except ValueError: print('No convergence reached') return def reset(self): self.reset_i() def subplots(self, fig): ax = fig.add_gridspec(1, 3) ax1 = fig.add_subplot(ax[0, 0:2], projection='3d') ax2 = fig.add_subplot(ax[0:, -1]) return ax1, ax2 def update_plot(self, axes): ax_sxy, ax_sig = axes self.plot_sig_w(ax_sig) ax_sig.set_xlabel(r'$w$ [mm]') ax_sig.set_ylabel(r'$\sigma$ [MPa]') # plot_tau_s(ax1, Eps_arr[-1,...],s_max,500,get_g3,**kw) ax_sxy.plot(self.s_x_t, self.s_y_t, 0, color='red', lw=1) self.plot3d_Sig_Eps(ax_sxy) ax_sxy.set_xlabel(r'$s_x$ [mm]') ax_sxy.set_ylabel(r'$s_y$ [mm]') ax_sxy.set_zlabel(r'$\| \tau \| = \sqrt{\tau_x^2 + \tau_y^2}$ [MPa]')
class GfDamageFn2(DamageFn): '''Class defining the damage function coupled with the fracture energy of a cohesive crack model. ''' name = 'damage function Gf' L_s = bu.Float(1.0, MAT=True, label="L_s", desc="Length of the softening zone") E_ = bu.Float(34000.0, MAT=True, label="E", desc="Young's modulus") E = Property(bu.Float) def _get_E(self): if self.mats: return getattr(self.mats, self.E_name) else: return self.E_ f_t = bu.Float(4.5, MAT=True, label="f_t", desc="Tensile strength") f_t_Em = Array(np.float_, value=None) G_f = bu.Float( 0.004, MAT=True, label="G_f", desc="Fracture energy", ) s_0 = Property(bu.Float) def _get_s_0(self): return self.f_t / self.E eps_ch = Property(bu.Float) def _get_eps_ch(self): return self.G_f / self.f_t ipw_view = bu.View( bu.Item('L_s', latex=r'L_\mathrm{s}'), bu.Item('f_t', latex=r'f_\mathrm{t}'), bu.Item('G_f', latex=r'G_\mathrm{f}'), bu.Item('E', readonly=True), bu.Item('s_0', latex=r's_0', readonly=True), ) def __call__(self, kappa): L_s = self.L_s f_t = self.f_t G_f = self.G_f E = self.E s_0 = self.s_0 return (1 - f_t * np.exp(-f_t * (kappa - s_0) * L_s / G_f) / (E * kappa)) def diff(self, kappa): L_s = self.L_s f_t = self.f_t G_f = self.G_f E = self.E s_0 = self.s_0 return (f_t * np.exp(L_s * (s_0 - kappa) * f_t / G_f) / (E * G_f * kappa**2) * (G_f + L_s * kappa * f_t))
class FRPDamageFn(DamageFn): name = 'FRP damage function' B = bu.Float(10.4, MAT=True, symbol="B", unit='mm$^{-1}$', desc="parameter controlling the damage maximum stress level") Gf = bu.Float(1.19, MAT=True, symbol="G_\mathrm{f}", unit='N/mm', desc="fracture energy") E_bond = bu.Float(0.0) E_b = Property(Float) def _get_E_b(self): return self.mats.E_b def _set_E_b(self, value): self.E_bond = value self.mats.E_b = value @observe('B, Gf') def _update_dependent_params(self, event=None): self.E_b = 1.734 * self.Gf * self.B**2.0 # calculation of s_0, implicit function solved using Newton method def f_s(s_0): return s_0 / \ (np.exp(- self.B * s_0) - np.exp(-2.0 * self.B * s_0)) - \ 2.0 * self.B * self.Gf / self.E_b self.s_0 = newton(f_s, 0.00000001, tol=1e-5, maxiter=20) def __call__(self, kappa): b = self.B Gf = self.Gf Eb = self.E_b # 1.734 * Gf * b**2 s_0 = self.s_0 # calculation of s_0, implicit function solved using Newton method # def f_s(s_0): return s_0 / \ # (np.exp(-b * s_0) - np.exp(-2.0 * b * s_0)) - 2.0 * b * Gf / Eb # s_0 = newton(f_s, 0.00000001, tol=1e-5, maxiter=20) omega = np.zeros_like(kappa, dtype=np.float_) I = np.where(kappa >= s_0)[0] kappa_I = kappa[I] omega[I] = 1 - \ (2.0 * b * Gf * (np.exp(-b * kappa_I) - np.exp(-2.0 * b * kappa_I))) / (kappa_I * Eb) return omega def diff(self, kappa): nz_ix = np.where(kappa != 0.0)[0] b = self.B Gf = self.Gf Eb = 1.734 * Gf * b**2 domega_dkappa = np.zeros_like(kappa) kappa_nz = kappa[nz_ix] domega_dkappa[nz_ix] = ( (2.0 * b * Gf * (np.exp(-b * kappa_nz) - np.exp(-2.0 * b * kappa_nz))) / (Eb * kappa_nz**2.0) - (2.0 * b * Gf * (-b * np.exp(-b * kappa_nz) + 2.0 * b * np.exp(-2.0 * b * kappa_nz))) / (Eb * kappa_nz)) return domega_dkappa latex_eq = r'''Damage function (FRP) \begin{align} \omega = g(\kappa) = 1 - {\frac {{\exp(-2\,Bs)}-{\exp(-Bs)}}{Bs}} \end{align} where $\kappa$ is the state variable representing the maximum slip that occurred so far in in the history of loading. ''' ipw_view = bu.View( bu.Item('s_0', readonly=True), bu.Item('E_bond', readonly=True), bu.Item('B'), bu.Item('Gf'), )
class WBShellAnalysis(TStepBC, bu.InteractiveModel): name = 'WBShellAnalysis' plot_backend = 'k3d' id = bu.Str """ if you saved boundary conditions for your current analysis, this id will make sure these bcs are loaded automatically next time you create an instance with the same id """ h = bu.Float(10, GEO=True) show_wireframe = bu.Bool(True, GEO=True) ipw_view = bu.View( bu.Item('h', editor=bu.FloatRangeEditor(low=1, high=100, n_steps=100), continuous_update=False), bu.Item('show_wireframe'), time_editor=bu.ProgressEditor(run_method='run', reset_method='reset', interrupt_var='interrupt', time_var='t', time_max='t_max'), ) n_phi_plus = tr.Property() def _get_n_phi_plus(self): return self.xdomain.mesh.n_phi_plus tree = ['geo', 'bcs', 'tmodel', 'xdomain'] geo = bu.Instance(WBShellGeometry4P, ()) tmodel = bu.Instance(MATS2DElastic, ()) # tmodel = bu.Instance(MATSShellElastic, ()) bcs = bu.Instance(BoundaryConditions) def _bcs_default(self): return BoundaryConditions(geo=self.geo, n_nodal_dofs=self.xdomain.fets.n_nodal_dofs, id=self.id) xdomain = tr.Property(tr.Instance(TriXDomainFE), depends_on="state_changed") '''Discretization object.''' @tr.cached_property def _get_xdomain(self): # prepare the mesh generator # mesh = WBShellFETriangularMesh(geo=self.geo, direct_mesh=False, subdivision=2) mesh = WBShellFETriangularMesh(geo=self.geo, direct_mesh=True) # construct the domain with the kinematic strain mapper and stress integrator return TriXDomainFE( mesh=mesh, integ_factor=self.h, ) # mesh = WBShellFETriangularMesh(geo=self.geo, direct_mesh=True) # mesh.fets = FETS2DMITC(a= self.h) # return TriXDomainMITC( # mesh=mesh # ) domains = tr.Property(depends_on="state_changed") @tr.cached_property def _get_domains(self): return [(self.xdomain, self.tmodel)] def reset(self): self.sim.reset() t = tr.Property() def _get_t(self): return self.sim.t def _set_t(self, value): self.sim.t = value t_max = tr.Property() def _get_t_max(self): return self.sim.t_max def _set_t_max(self, value): self.sim.t_max = value interrupt = tr.Property() def _get_interrupt(self): return self.sim.interrupt def _set_interrupt(self, value): self.sim.interrupt = value bc = tr.Property(depends_on="state_changed") # @tr.cached_property def _get_bc(self): bc_fixed, _, _ = self.bcs.bc_fixed bc_loaded, _, _ = self.bcs.bc_loaded return bc_fixed + bc_loaded def run(self): s = self.sim s.tloop.k_max = 10 s.tline.step = 1 s.tloop.verbose = False s.run() def get_max_vals(self): self.run() U_1 = self.hist.U_t[-1] U_max = np.max(np.fabs(U_1)) return U_max def export_abaqus(self): al = AbaqusLink(shell_analysis=self) al.model_name = 'test_name' al.build_inp() def setup_plot(self, pb): print('analysis: setup_plot') X_Id = self.xdomain.mesh.X_Id if len(self.hist.U_t) == 0: U_1 = np.zeros_like(X_Id) print('analysis: U_I', ) else: U_1 = self.hist.U_t[-1] U_1 = U_1.reshape(-1, self.xdomain.fets.n_nodal_dofs)[:, :3] X1_Id = X_Id + U_1 X1_Id = X1_Id.astype(np.float32) I_Ei = self.xdomain.I_Ei.astype(np.uint32) # Original state mesh wb_mesh_0 = k3d.mesh(self.xdomain.X_Id.astype(np.float32), I_Ei, color=0x999999, opacity=0.5, side='double') pb.plot_fig += wb_mesh_0 pb.objects['wb_mesh_0'] = wb_mesh_0 # Deformed state mesh wb_mesh_1 = k3d.mesh(X1_Id, I_Ei, color_map=k3d.colormaps.basic_color_maps.Jet, attribute=U_1[:, 2], color_range=[np.min(U_1), np.max(U_1)], side='double') pb.plot_fig += wb_mesh_1 pb.objects['wb_mesh_1'] = wb_mesh_1 if self.show_wireframe: k3d_mesh_wireframe = k3d.mesh(X1_Id, I_Ei, color=0x000000, wireframe=True) pb.plot_fig += k3d_mesh_wireframe pb.objects['mesh_wireframe'] = k3d_mesh_wireframe def update_plot(self, pb): X_Id = self.xdomain.mesh.X_Id print('analysis: update_plot') if len(self.hist.U_t) == 0: U_1 = np.zeros_like(X_Id) print('analysis: U_I', ) else: U_1 = self.hist.U_t[-1] U_1 = U_1.reshape(-1, self.xdomain.fets.n_nodal_dofs)[:, :3] X1_Id = X_Id + U_1 X1_Id = X1_Id.astype(np.float32) I_Ei = self.xdomain.I_Ei.astype(np.uint32) mesh = pb.objects['wb_mesh_1'] mesh.vertices = X1_Id mesh.indices = I_Ei mesh.attribute = U_1[:, 2] mesh.color_range = [np.min(U_1), np.max(U_1)] if self.show_wireframe: wireframe = pb.objects['mesh_wireframe'] wireframe.vertices = X1_Id wireframe.indices = I_Ei def get_Pw(self): import numpy as np F_to = self.hist.F_t U_to = self.hist.U_t _, _, loaded_dofs = self.bcs.bc_loaded F_loaded = np.sum(F_to[:, loaded_dofs], axis=-1) U_loaded = np.average(U_to[:, loaded_dofs], axis=-1) return U_loaded, F_loaded
class WBTessellationBase(bu.Model): name = 'WB Tessellation Base' plot_backend = 'k3d' # show_wireframe = bu.Bool(True, GEO=True) show_node_labels = bu.Bool(False, GEO=True) wb_cell = bu.EitherType(options=[('WBCell4Param', WBCell4Param), ('WBCell5Param', WBCell5Param), ('WBCell5ParamV2', WBCell5ParamV2), ('WBCell5ParamV3', WBCell5ParamV3)], GEO=True) X_Ia = tr.DelegatesTo('wb_cell_') I_Fi = tr.DelegatesTo('wb_cell_') tree = ['wb_cell'] event_geo = bu.Bool(True, GEO=True) # Note: Update traits to 6.3.2 in order for the following command to work!! @tr.observe('wb_cell_.+GEO', post_init=True) def update_after_wb_cell_GEO_changes(self, event): self.event_geo = not self.event_geo self.update_plot(self.pb) ipw_view = bu.View( bu.Item('wb_cell'), # bu.Item('show_wireframe'), bu.Item('show_node_labels'), ) def _get_br_X_Ia(self, X_Ia, rot=None): br_X_Ia = self._get_cell_matching_v1_to_v2(X_Ia, np.array([4, 6]), np.array([5, 1])) return self.rotate_cell(br_X_Ia, np.array([4, 6]), self.sol[0] if rot is None else rot) def _get_ur_X_Ia(self, X_Ia, rot=None): ur_X_Ia = self._get_cell_matching_v1_to_v2(X_Ia, np.array([6, 2]), np.array([3, 5])) return self.rotate_cell(ur_X_Ia, np.array([6, 2]), self.sol[1] if rot is None else rot) def _get_ul_X_Ia(self, X_Ia, rot=None): br_X_Ia = self._get_cell_matching_v1_to_v2(X_Ia, np.array([5, 1]), np.array([4, 6])) return self.rotate_cell(br_X_Ia, np.array([5, 1]), -self.sol[0] if rot is None else rot) def _get_bl_X_Ia(self, X_Ia, rot=None): br_X_Ia = self._get_cell_matching_v1_to_v2(X_Ia, np.array([3, 5]), np.array([6, 2])) return self.rotate_cell(br_X_Ia, np.array([3, 5]), -self.sol[1] if rot is None else rot) def _get_cell_matching_v1_to_v2(self, X_Ia, v1_ids, v2_ids): v1_2a = np.array([X_Ia[v1_ids[0]], X_Ia[v1_ids[1]], X_Ia[0]]).T v2_2a = np.array([ X_Ia[v2_ids[0]], X_Ia[v2_ids[1]], X_Ia[v2_ids[0]] + X_Ia[v2_ids[1]] - X_Ia[0] ]).T rot, trans = get_best_rot_and_trans_3d(v1_2a, v2_2a) translated_X_Ia = trans.flatten() + np.einsum('ba, Ia -> Ib', rot, X_Ia) return self.rotate_cell(translated_X_Ia, v1_ids, angle=np.pi) def rotate_cell(self, cell_X_Ia, v1_ids, angle=np.pi): # Rotating around vector ####### # 1. Bringing back to origin (because rotating is around a vector originating from origin) cell_X_Ia_copy = np.copy(cell_X_Ia) cell_X_Ia = cell_X_Ia_copy - cell_X_Ia_copy[v1_ids[1]] # 2. Rotating rot_around_v1 = get_rot_matrix_around_vector( cell_X_Ia[v1_ids[0]] - cell_X_Ia[v1_ids[1]], angle) cell_X_Ia = np.einsum('ba, Ia -> Ib', rot_around_v1, cell_X_Ia) # 3. Bringing back in position return cell_X_Ia + cell_X_Ia_copy[v1_ids[1]] sol = tr.Property(depends_on='+GEO') @tr.cached_property def _get_sol(self): # No solution is provided in base class, a default value is provided for visualization return np.array([np.pi, np.pi]) # Plotting ########################################################################## def setup_plot(self, pb): self.pb = pb pb.clear_fig() I_Fi = self.I_Fi X_Ia = self.X_Ia br_X_Ia = self._get_br_X_Ia(X_Ia) ur_X_Ia = self._get_ur_X_Ia(X_Ia) self.add_cell_to_pb(pb, X_Ia, I_Fi, 'X_Ia') self.add_cell_to_pb(pb, br_X_Ia, I_Fi, 'br_X_Ia') self.add_cell_to_pb(pb, ur_X_Ia, I_Fi, 'ur_X_Ia') k3d_mesh = {} k3d_wireframe = {} k3d_labels = {} def update_plot(self, pb): if self.k3d_mesh: X_Ia = self.X_Ia.astype(np.float32) br_X_Ia = self._get_br_X_Ia(self.X_Ia).astype(np.float32) ur_X_Ia = self._get_ur_X_Ia(self.X_Ia).astype(np.float32) self.k3d_mesh['X_Ia'].vertices = X_Ia self.k3d_mesh['br_X_Ia'].vertices = br_X_Ia self.k3d_mesh['ur_X_Ia'].vertices = ur_X_Ia self.k3d_wireframe['X_Ia'].vertices = X_Ia self.k3d_wireframe['br_X_Ia'].vertices = br_X_Ia self.k3d_wireframe['ur_X_Ia'].vertices = ur_X_Ia else: self.setup_plot(pb) def add_cell_to_pb(self, pb, X_Ia, I_Fi, obj_name): plot = pb.plot_fig wb_mesh = k3d.mesh( X_Ia.astype(np.float32), I_Fi.astype(np.uint32), # opacity=0.9, color=0x999999, side='double') rand_color = random.randint(0, 0xFFFFFF) plot += wb_mesh self.k3d_mesh[obj_name] = wb_mesh # wb_points = k3d.points(X_Ia.astype(np.float32), # color=0x999999, # point_size=100) # plot +=wb_points if self.show_node_labels: texts = [] for I, X_a in enumerate(X_Ia): k3d_text = k3d.text('%g' % I, tuple(X_a), label_box=False, size=0.8, color=rand_color) plot += k3d_text texts.append(k3d_text) self.k3d_labels[obj_name] = texts wb_mesh_wireframe = k3d.mesh(X_Ia.astype(np.float32), I_Fi.astype(np.uint32), color=0x000000, wireframe=True) plot += wb_mesh_wireframe self.k3d_wireframe[obj_name] = wb_mesh_wireframe
class MATS3DScalarDamage(MATS3DEval): r''' Isotropic damage model. ''' node_name = 'Scalar damage' stiffness = tr.Enum("secant", "algorithmic", input=True) r'''Selector of the stiffness calculation. ''' strain_norm = EitherType(klasses=[ Rankine, ], MAT=True) r'''Selector of the strain norm defining the load surface. ''' # TODO - generalize # damage_fn = EitherType(klasses=[], MAT=True) # r'''Selector of the damage function. # ''' epsilon_0 = bu.Float(5e-2, label="eps_0", desc="Strain at the onset of damage", auto_set=False, MAT=True) r'''Damage function parameter - slope of the damage function. ''' epsilon_f = bu.Float(191e-1, label="eps_f", desc="Slope of the damage function", auto_set=False, MAT=True) r'''Damage function parameter - slope of the damage function. ''' ipw_view = bu.View(bu.Item('epsilon_0'), bu.Item('epsilon_f')) changed = tr.Event r'''This event can be used by the clients to trigger an action upon the completed reconfiguration of the material model ''' 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 init(self, kappa, omega): r''' Initialize the state variables. ''' kappa[...] = 0 omega[...] = 0 def get_corr_pred(self, eps_Emab_n1, tn1, kappa, omega): r''' Corrector predictor computation. ''' self.update_state_variables(eps_Emab_n1, kappa, omega) phi_Em = (1.0 - omega) D_Emabcd = np.einsum('...,abcd->...abcd', phi_Em, self.D_abef) sigma_Emab = np.einsum('...abcd,...cd->...ab', D_Emabcd, eps_Emab_n1) # algorithmic switched off - because the derivative # of the strain norm is still not available if False: # algorithmic: D_Emabcd_red_I = self._get_D_abcd_alg_reduction( kappa[I], eps_Emab_n1[I]) D_Emabcd[I] -= D_Emabcd_red_I return sigma_Emab, D_Emabcd def update_state_variables(self, eps_Emab, kappa, omega): eps_eq_Em = self.strain_norm.get_eps_eq(eps_Emab, kappa) f_trial_Em = eps_eq_Em - self.epsilon_0 I = np.where(f_trial_Em > 0) kappa[I] = eps_eq_Em[I] omega[I] = self._get_omega(eps_eq_Em[I]) return I def _get_omega(self, kappa_Em): r''' Return new value of damage parameter @param kappa_Em: maximum strain norm achieved so far ''' omega_Em = np.zeros_like(kappa_Em) epsilon_0 = self.epsilon_0 epsilon_f = self.epsilon_f I = np.where(kappa_Em >= epsilon_0) omega_Em[I] = ( 1.0 - (epsilon_0 / kappa_Em[I] * np.exp(-1.0 * (kappa_Em[I] - epsilon_0) / (epsilon_f - epsilon_0)))) return omega_Em def _get_domega(self, kappa_Em): ''' Return new value of damage parameter derivative @param kappa_Em: maximum strain norm achieved so far ''' epsilon_0 = self.epsilon_0 epsilon_f = self.epsilon_f domega_Em = np.zeros_like(kappa_Em) I = np.where(kappa_Em >= epsilon_0) factor_1 = epsilon_0 / (kappa_Em[I] * kappa_Em[I]) factor_2 = epsilon_0 / (kappa_Em[I] * (epsilon_f - epsilon_0)) domega_Em[I] = ((factor_1 + factor_2) * np.exp(-(kappa_Em[I] - epsilon_0) / (epsilon_f - epsilon_0))) return domega_Em def _get_D_abcd_alg_reduction(self, kappa_Em, eps_Emab_n1): '''Calculate the stiffness term to be subtracted from the secant stiffness to get the algorithmic stiffness. ''' domega_Em = self._get_domega(kappa_Em) deps_eq_Emcd = self.strain_norm.get_deps_eq(eps_Emab_n1) return np.einsum('...,...cd,abcd,...cd->...abcd', domega_Em, deps_eq_Emcd, self.D_abef, eps_Emab_n1) traits_view = View(VSplit( Group(Item('E'), Item('nu'), Item('epsilon_0'), Item('epsilon_f'), Item('strain_norm')), Group( Item('stiffness', style='custom'), Spring(resizable=True), label='Configuration parameters', show_border=True, ), ), resizable=True) tree_view = View( Group(Item('E', full_size=True, resizable=True), Item('nu'), Item('epsilon_0'), Item('epsilon_f'), Item('strain_norm')), ) # Declare and fill-in the rte_dict - it is used by the clients to # assemble all the available time-steppers. # rte_dict = tr.Trait(tr.Dict) def _rte_dict_default(self): return {'omega': self.get_omega}
class FETriangularMesh(bu.Model): name = 'FETriangularMesh' X_Id = tr.Array(np.float_, value=[[0, 0, 0], [2, 0, 0], [2, 2, 0], [1, 1, 0]]) I_Fi = tr.Array(np.int_, value=[ [0, 1, 3], [1, 2, 3], ]) fets = tr.Instance(FETSEval) def _fets_default(self): return FETS2D3U1M() show_node_labels = bu.Bool(False) n_nodal_dofs = tr.DelegatesTo('fets') dof_offset = tr.Int(0) n_active_elems = tr.Property def _get_n_active_elems(self): return len(self.I_Fi) ipw_view = bu.View(bu.Item('show_node_labels'), ) #========================================================================= # 3d Visualization #========================================================================= plot_backend = 'k3d' show_wireframe = bu.Bool(True) def setup_plot(self, pb): X_Id = self.X_Id.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) fe_mesh = k3d.mesh(X_Id, I_Fi, color=0x999999, opacity=1.0, side='double') pb.plot_fig += fe_mesh pb.objects['mesh'] = fe_mesh if self.show_wireframe: k3d_mesh_wireframe = k3d.mesh(X_Id, I_Fi, color=0x000000, wireframe=True) pb.plot_fig += k3d_mesh_wireframe pb.objects['mesh_wireframe'] = k3d_mesh_wireframe if self.show_node_labels: self._add_nodes_labels_to_fig(pb, X_Id) NODES_LABELS = 'nodes_labels' def update_plot(self, pb): X_Id = self.X_Id.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) mesh = pb.objects['mesh'] mesh.vertices = X_Id mesh.indices = I_Fi if self.show_wireframe: wireframe = pb.objects['mesh_wireframe'] wireframe.vertices = X_Id wireframe.indices = I_Fi if self.show_node_labels: if self.NODES_LABELS in pb.objects: pb.clear_object(self.NODES_LABELS) self._add_nodes_labels_to_fig(pb, X_Id) else: if self.NODES_LABELS in pb.objects: pb.clear_object(self.NODES_LABELS) def _add_nodes_labels_to_fig(self, pb, X_Id): text_list = [] for I, X_d in enumerate(X_Id): k3d_text = k3d.text('%g' % I, tuple(X_d), label_box=False, size=0.8, color=0x00FF00) pb.plot_fig += k3d_text text_list.append(k3d_text) pb.objects[self.NODES_LABELS] = text_list
class Slide34(MATSEval, bu.InjectSymbExpr): name = 'Slide 3.4' symb_class = Slide34Expr E_T = bu.Float(28000, MAT=True) gamma_T = bu.Float(10, MAT=True) K_T = bu.Float(8, MAT=True) S_T = bu.Float(1, MAT=True) c_T = bu.Float(1, MAT=True) bartau = bu.Float(28000, MAT=True) E_N = bu.Float(28000, MAT=True) S_N = bu.Float(1, MAT=True) c_N = bu.Float(1, MAT=True) m = bu.Float(0.1, MAT=True) f_t = bu.Float(3, MAT=True) f_c = bu.Float(30, MAT=True) f_c0 = bu.Float(20, MAT=True) eta = bu.Float(0.5, MAT=True) r = bu.Float(1, MAT=True) c_NT = tr.Property(bu.Float, depends_on='state_changed') @tr.cached_property def _get_c_NT(self): return np.sqrt(self.c_N * self.c_T) S_NT = tr.Property(bu.Float, depends_on='state_changed') @tr.cached_property def _get_S_NT(self): return np.sqrt(self.S_N * self.S_T) debug = bu.Bool(False) def C_codegen(self): import os import os.path as osp C_code = [] for symb_name, symb_params in self.symb.symb_expressions: c_func_name = 'get_' + symb_name c_func = ccode(c_func_name, getattr(self.symb, symb_name), 'SLIDE33') C_code.append(c_func) code_dirname = 'sympy_codegen' code_fname = 'SLIDE33_3D' home_dir = osp.expanduser('~') code_dir = osp.join(home_dir, code_dirname) if not osp.exists(code_dir): os.makedirs(code_dir) code_file = osp.join(code_dir, code_fname) print('generated code_file', code_file) h_file = code_file + '.h' c_file = code_file + '.c' h_f = open(h_file, 'w') c_f = open(c_file, 'w') if True: for function_C in C_code: h_f.write(function_C[1][1]) c_f.write(function_C[0][1]) h_f.close() c_f.close() ipw_view = bu.View( bu.Item('E_T', latex='E_T'), bu.Item('S_T'), bu.Item('c_T'), bu.Item('gamma_T'), bu.Item('K_T'), bu.Item('bartau', latex=r'\bar{\tau}'), bu.Item('E_N'), bu.Item('S_N'), bu.Item('c_N'), bu.Item('m'), bu.Item('f_t'), bu.Item('f_c', latex=r'f_\mathrm{c}'), bu.Item('f_c0', latex=r'f_\mathrm{c0}'), bu.Item('eta'), bu.Item('r'), bu.Item('c_NT', readonly=True), bu.Item('S_NT', readonly=True), ) damage_interaction = tr.Enum('final', 'geometric', 'arithmetic') get_phi_ = tr.Property def _get_get_phi_(self): return self.symb.get_phi_final_ get_Phi_ = tr.Property def _get_get_Phi_(self): return self.symb.get_Phi_final_ def get_f_df(self, u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k): if self.debug: print('w_n1', u_N_n1.dtype, u_N_n1.shape) print('s_x_n1', u_Tx_n1.dtype, u_Tx_n1.shape) print('s_y_n1', u_Ty_n1.dtype, u_Ty_n1.shape) print('Eps_k', Eps_k.dtype, Eps_k.shape) print('Sig_k', Sig_k.dtype, Sig_k.shape) ONES = np.ones_like(u_Tx_n1, dtype=np.float_) if self.debug: print('ONES', ONES.dtype) ZEROS = np.zeros_like(u_Tx_n1, dtype=np.float_) if self.debug: print('ZEROS', ZEROS.dtype) Sig_k = self.symb.get_Sig_(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k)[0] if self.debug: print('Sig_k', Sig_k.dtype, Sig_k.shape) dSig_dEps_k = self.symb.get_dSig_dEps_(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k, ZEROS, ONES) if self.debug: print('dSig_dEps_k', dSig_dEps_k.dtype) H_sig_pi = self.symb.get_H_sig_pi_(Sig_k) if self.debug: print('H_sig_pi', H_sig_pi.dtype) f_k = np.array([self.symb.get_f_(Eps_k, Sig_k, H_sig_pi)]) if self.debug: print('f_k', f_k.dtype) df_dSig_k = self.symb.get_df_dSig_(Eps_k, Sig_k, H_sig_pi, ZEROS, ONES) if self.debug: print('df_dSig_k', df_dSig_k.dtype) ddf_dEps_k = self.symb.get_ddf_dEps_(Eps_k, Sig_k, H_sig_pi, ZEROS, ONES) if self.debug: print('ddf_dEps_k', ddf_dEps_k.dtype) df_dEps_k = np.einsum('ik...,ji...->jk...', df_dSig_k, dSig_dEps_k) + ddf_dEps_k Phi_k = self.get_Phi_(Eps_k, Sig_k, H_sig_pi, ZEROS, ONES) dEps_dlambda_k = Phi_k df_dlambda = np.einsum('ki...,kj...->ij...', df_dEps_k, dEps_dlambda_k) df_k = df_dlambda return f_k, df_k, Sig_k def get_Eps_k1(self, u_N_n1, u_Tx_n1, u_Ty_n1, Eps_n, lam_k, Sig_k, Eps_k): '''Evolution equations: The update of state variables for an updated $\lambda_k$ is performed using this procedure. ''' ONES = np.ones_like(u_Tx_n1) ZEROS = np.zeros_like(u_Tx_n1) Sig_k = self.symb.get_Sig_(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k)[0] H_sig_pi = self.symb.get_H_sig_pi_(Sig_k) Phi_k = self.get_Phi_(Eps_k, Sig_k, H_sig_pi, ZEROS, ONES) Eps_k1 = Eps_n + lam_k * Phi_k[:, 0] return Eps_k1 rtol = bu.Float(1e-3, ALG=True) '''Relative tolerance of the return mapping algorithm related to the tensile strength ''' Eps_names = tr.Property @tr.cached_property def _get_Eps_names(self): return [eps.codename for eps in self.symb.Eps] Sig_names = tr.Property @tr.cached_property def _get_Sig_names(self): return [sig.codename for sig in self.symb.Sig] state_var_shapes = tr.Property @tr.cached_property def _get_state_var_shapes(self): '''State variables shapes: variables are using the codename string in the Cymbol definition Since the same string is used in the lambdify method via print_Symbol method defined in Cymbol as well''' return {eps_name: () for eps_name in self.Eps_names + self.Sig_names} k_max = bu.Int(100, ALG=True) '''Maximum number of iterations''' def get_corr_pred(self, eps_Ema, t_n1, **state): '''Return mapping iteration: This function represents a user subroutine in a finite element code or in a lattice model. The input is $s_{n+1}$ and the state variables representing the state in the previous solved step $\boldsymbol{\mathcal{E}}_n$. The procedure returns the stresses and state variables of $\boldsymbol{\mathcal{S}}_{n+1}$ and $\boldsymbol{\mathcal{E}}_{n+1}$ ''' eps_aEm = np.einsum('...a->a...', eps_Ema) dim = len(eps_aEm) if dim == 2: # hack - only one slip considered - 2D version select_idx = (1, 0) u_Tx_n1, u_N_n1 = eps_aEm u_Ty_n1 = np.zeros_like(u_Tx_n1) else: raise ValueError('3D not implemented here') ONES = np.ones_like(u_Tx_n1, dtype=np.float_) if self.debug: print('ONES', ONES.dtype) ZEROS = np.zeros_like(u_Tx_n1, dtype=np.float_) if self.debug: print('ZEROS', ZEROS.dtype) # Transform state to Eps_k and Sig_k Eps_n = np.array([state[eps_name] for eps_name in self.Eps_names], dtype=np.float_) Eps_k = np.copy(Eps_n) #Sig_k = np.array([state[sig_name] for sig_name in self.Sig_names], dtype=np.float_) Sig_k = np.zeros_like(Eps_k) f_k, df_k, Sig_k = self.get_f_df(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k) f_k, df_k = f_k[0, ...], df_k[0, 0, ...] f_k_trial = f_k # indexes of inelastic entries L = np.where(f_k_trial > 0) # f norm in inelastic entries - to allow also positive values less the rtol f_k_norm_I = np.fabs(f_k_trial[L]) lam_k = np.zeros_like(f_k_trial) k = 0 while k < self.k_max: if self.debug: print('k', k) # which entries are above the tolerance I = np.where(f_k_norm_I > (self.f_t * self.rtol)) if self.debug: print('f_k_norm_I', f_k_norm_I, self.f_t * self.rtol, len(I[0])) if (len(I[0]) == 0): # empty inelastic entries - accept state #return Eps_k, Sig_k, k + 1 dSig_dEps_k = self.symb.get_dSig_dEps_(u_N_n1, u_Tx_n1, u_Ty_n1, Sig_k, Eps_k, ZEROS, ONES) ix1, ix2 = np.ix_(select_idx, select_idx) D_ = np.einsum('ab...->...ab', dSig_dEps_k[ix1, ix2, ...]) sig_ = np.einsum('a...->...a', Sig_k[select_idx, ...]) # quick fix _, _, _, _, _, _, omega_T, omega_N = Eps_k D_ = np.zeros(sig_.shape + (sig_.shape[-1], )) D_[..., 0, 0] = self.E_N * (1 - omega_N) D_[..., 1, 1] = self.E_T * (1 - omega_T) if dim == 3: D_[..., 2, 2] = self.E_T #* (1 - omega_T) for eps_name, Eps_ in zip(self.Eps_names, Eps_k): state[eps_name][...] = Eps_[...] for sig_name, Sig_ in zip(self.Sig_names, Sig_k): state[sig_name][...] = Sig_[...] return sig_, D_ if self.debug: print('I', I) print('L', L) LL = tuple(Li[I] for Li in L) L = LL if self.debug: print('new L', L) print('f_k', f_k[L].shape, f_k[L].dtype) print('df_k', df_k[L].shape, df_k[L].dtype) # return mapping on inelastic entries dlam_L = -f_k[L] / df_k[L] # np.linalg.solve(df_k[I], -f_k[I]) if self.debug: print('dlam_I', dlam_L, dlam_L.dtype) lam_k[L] += dlam_L if self.debug: print('lam_k_L', lam_k, lam_k.dtype, lam_k[L].shape) L_slice = (slice(None), ) + L Eps_k_L = self.get_Eps_k1(u_N_n1[L], u_Tx_n1[L], u_Ty_n1[L], Eps_n[L_slice], lam_k[L], Sig_k[L_slice], Eps_k[L_slice]) Eps_k[L_slice] = Eps_k_L f_k_L, df_k_L, Sig_k_L = self.get_f_df(u_N_n1[L], u_Tx_n1[L], u_Ty_n1[L], Sig_k[L_slice], Eps_k_L) f_k[L], df_k[L] = f_k_L[0, ...], df_k_L[0, 0, ...] Sig_k[L_slice] = Sig_k_L if self.debug: print('Sig_k', Sig_k) print('f_k', f_k) f_k_norm_I = np.fabs(f_k[L]) k += 1 else: raise ConvergenceError('no convergence for entries', [L, u_N_n1[I], u_Tx_n1[I], u_Ty_n1[I]]) # add the algorithmic stiffness # recalculate df_k and -f_k for a unit increment of epsilon and solve for lambda # def plot_f_state(self, ax, Eps, Sig): lower = -self.f_c * 1.05 upper = self.f_t + 0.05 * self.f_c lower_tau = -self.bartau * 2 upper_tau = self.bartau * 2 lower_tau = 0 upper_tau = 10 sig, tau_x, tau_y = Sig[:3] tau = np.sqrt(tau_x**2 + tau_y**2) sig_ts, tau_x_ts = np.mgrid[lower:upper:201j, lower_tau:upper_tau:201j] Sig_ts = np.zeros((len(self.symb.Eps), ) + tau_x_ts.shape) Eps_ts = np.zeros_like(Sig_ts) Sig_ts[0, ...] = sig_ts Sig_ts[1, ...] = tau_x_ts Sig_ts[3:, ...] = Sig[3:, np.newaxis, np.newaxis] Eps_ts[...] = Eps[:, np.newaxis, np.newaxis] H_sig_pi = self.symb.get_H_sig_pi_(Sig_ts) f_ts = np.array([self.symb.get_f_(Eps_ts, Sig_ts, H_sig_pi)]) #phi_ts = np.array([self.symb.get_phi_(Eps_ts, Sig_ts)]) ax.set_title('threshold function') omega_N = Eps_ts[-1, :] omega_T = Eps_ts[-2, :] sig_ts_eff = sig_ts / (1 - H_sig_pi * omega_N) tau_x_ts_eff = tau_x_ts / (1 - omega_T) ax.contour(sig_ts_eff, tau_x_ts_eff, f_ts[0, ...], levels=0, colors=('green', )) ax.contour(sig_ts, tau_x_ts, f_ts[0, ...], levels=0, colors=('red', )) #ax.contour(sig_ts, tau_x_ts, phi_ts[0, ...]) ax.plot(sig, tau, marker='H', color='red') ax.plot([lower, upper], [0, 0], color='black', lw=0.4) ax.plot([0, 0], [lower_tau, upper_tau], color='black', lw=0.4) ax.set_ylim(ymin=0, ymax=10) def plot_f(self, ax): lower = -self.f_c * 1.05 upper = self.f_t + 0.05 * self.f_c lower_tau = -self.bartau * 2 upper_tau = self.bartau * 2 sig_ts, tau_x_ts = np.mgrid[lower:upper:201j, lower_tau:upper_tau:201j] Sig_ts = np.zeros((len(self.symb.Eps), ) + tau_x_ts.shape) Sig_ts[0, :] = sig_ts Sig_ts[1, :] = tau_x_ts Eps_ts = np.zeros_like(Sig_ts) H_sig_pi = self.symb.get_H_sig_pi_(Sig_ts) f_ts = np.array([self.symb.get_f_(Eps_ts, Sig_ts, H_sig_pi)]) phi_ts = np.array([self.get_phi_(Eps_ts, Sig_ts, H_sig_pi)]) ax.set_title('threshold function') ax.contour(sig_ts, tau_x_ts, f_ts[0, ...], levels=0) ax.contour(sig_ts, tau_x_ts, phi_ts[0, ...]) ax.plot([lower, upper], [0, 0], color='black', lw=0.4) ax.plot([0, 0], [lower_tau, upper_tau], color='black', lw=0.4) def plot_sig_w(self, ax): pass def plot_tau_s(self, ax): pass def subplots(self, fig): return fig.subplots(2, 2) def update_plot(self, axes): (ax_sig_w, ax_tau_s), (ax_f, _) = axes self.plot_sig_w(ax_sig_w) self.plot_tau_s(ax_tau_s) self.plot_f(ax_f)
class WBTessellation4P(bu.Model): name = 'WB Tessellation 4P' wb_cell = bu.Instance(WBCell4Param) def _wb_cell_default(self): wb_cell = WBCell4Param() self.update_wb_cell_params(wb_cell) return wb_cell tree = ['wb_cell'] plot_backend = 'k3d' n_phi_plus = bu.Int(5, GEO=True) n_x_plus = bu.Int(3, GEO=True) gamma = bu.Float(1.25, GEO=True) a = bu.Float(1000, GEO=True) a_high = bu.Float(2000) b = bu.Float(1000, GEO=True) b_high = bu.Float(2000) c = bu.Float(1000, GEO=True) c_high = bu.Float(2000) show_wireframe = bu.Bool(True, GEO=True) show_nodes = bu.Bool(False, GEO=True) show_node_labels = bu.Bool(False, GEO=True) WIREFRAME = 'k3d_mesh_wireframe' NODES = 'k3d_nodes' NODES_LABELS = 'k3d_nodes_labels' @tr.observe('+GEO', post_init=True) def update_wb_cell(self, event): self.update_wb_cell_params(self.wb_cell) def update_wb_cell_params(self, wb_cell): wb_cell.trait_set( gamma=self.gamma, a=self.a, a_high=self.a_high, b=self.b, b_high=self.b_high, c=self.c, c_high=self.c_high, ) ipw_view = bu.View( # bu.Item('wb_cell'), *WBCell4Param.ipw_view.content, bu.Item('n_phi_plus', latex=r'n_\phi'), bu.Item('n_x_plus', latex=r'n_x'), # bu.Item('show_wireframe'), # bu.Item('show_node_labels'), bu.Item('show_nodes'), ) def get_phi_range(self, delta_phi): return np.arange(-(self.n_phi_plus - 1), self.n_phi_plus) * delta_phi def get_X_phi_range(self, delta_phi, R_0): """Given an array of angles and radius return an array of coordinates """ phi_range = self.get_phi_range((delta_phi)) return np.array([ np.fabs(R_0) * np.sin(phi_range), np.fabs(R_0) * np.cos(phi_range) + R_0 ]).T def get_X_x_range(self, delta_x): return np.arange(-(self.n_x_plus - 1), self.n_x_plus) * delta_x cell_map = tr.Property def _get_cell_map(self): delta_x = self.wb_cell.delta_x delta_phi = self.wb_cell.delta_phi R_0 = self.wb_cell.R_0 X_x_range = self.get_X_x_range(delta_x) X_phi_range = self.get_X_phi_range(delta_phi, R_0) n_idx_x = len(X_x_range) n_idx_phi = len(X_phi_range) idx_x = np.arange(n_idx_x) idx_phi = np.arange(n_idx_phi) idx_x_ic = idx_x[(n_idx_x) % 2::2] idx_x_id = idx_x[(n_idx_x + 1) % 2::2] idx_phi_ic = idx_phi[(n_idx_phi) % 2::2] idx_phi_id = idx_phi[(n_idx_phi + 1) % 2::2] n_ic = len(idx_x_ic) * len(idx_phi_ic) n_id = len(idx_x_id) * len(idx_phi_id) n_cells = n_ic + n_id return n_cells, n_ic, n_id, idx_x_ic, idx_x_id, idx_phi_ic, idx_phi_id n_cells = tr.Property def _get_n_cells(self): n_cells, _, _, _, _, _, _ = self.cell_map return n_cells X_cells_Ia = tr.Property(depends_on='+GEO') '''Array with nodal coordinates of uncoupled cells I - node, a - dimension ''' @tr.cached_property def _get_X_cells_Ia(self): delta_x = self.wb_cell.delta_x delta_phi = self.wb_cell.delta_phi R_0 = self.wb_cell.R_0 X_Ia_wb_rot = np.copy(self.wb_cell.X_Ia) X_Ia_wb_rot[..., 2] -= R_0 X_cIa = np.array([X_Ia_wb_rot], dtype=np.float_) rotation_axes = np.array([[1, 0, 0]], dtype=np.float_) rotation_angles = self.get_phi_range(delta_phi) q = axis_angle_to_q(rotation_axes, rotation_angles) X_dIa = qv_mult(q, X_cIa) X_dIa[..., 2] += R_0 X_x_range = self.get_X_x_range(delta_x) X_phi_range = self.get_X_phi_range(delta_phi, R_0) n_idx_x = len(X_x_range) n_idx_phi = len(X_phi_range) idx_x = np.arange(n_idx_x) idx_phi = np.arange(n_idx_phi) idx_x_ic = idx_x[(n_idx_x) % 2::2] idx_x_id = idx_x[(n_idx_x + 1) % 2::2] idx_phi_ic = idx_phi[(n_idx_phi) % 2::2] idx_phi_id = idx_phi[(n_idx_phi + 1) % 2::2] X_E = X_x_range[idx_x_ic] X_F = X_x_range[idx_x_id] X_CIa = X_dIa[idx_phi_ic] X_DIa = X_dIa[idx_phi_id] expand = np.array([1, 0, 0]) X_E_a = np.einsum('i,j->ij', X_E, expand) X_ECIa = X_CIa[np.newaxis, :, :, :] + X_E_a[:, np.newaxis, np.newaxis, :] X_F_a = np.einsum('i,j->ij', X_F, expand) X_FDIa = X_DIa[np.newaxis, :, :, :] + X_F_a[:, np.newaxis, np.newaxis, :] X_Ia = np.vstack( [X_ECIa.flatten().reshape(-1, 3), X_FDIa.flatten().reshape(-1, 3)]) return X_Ia I_cells_Fi = tr.Property(depends_on='+GEO') '''Array with nodal coordinates I - node, a - dimension ''' @tr.cached_property def _get_I_cells_Fi(self): I_Fi_cell = self.wb_cell.I_Fi n_I_cell = self.wb_cell.n_I n_cells = self.n_cells i_range = np.arange(n_cells) * n_I_cell I_Fi = (I_Fi_cell[np.newaxis, :, :] + i_range[:, np.newaxis, np.newaxis]).reshape(-1, 3) return I_Fi X_Ia = tr.Property(depends_on='+GEO') '''Array with nodal coordinates I - node, a - dimension ''' @tr.cached_property def _get_X_Ia(self): idx_unique, idx_remap = self.unique_node_map return self.X_cells_Ia[idx_unique] I_Fi = tr.Property(depends_on='+GEO') '''Facet - node mapping ''' @tr.cached_property def _get_I_Fi(self): _, idx_remap = self.unique_node_map return idx_remap[self.I_cells_Fi] node_match_threshold = tr.Property(depends_on='+GEO') def _get_node_match_threshold(self): min_length = np.min([self.a, self.b, self.c]) return min_length * 1e-4 unique_node_map = tr.Property(depends_on='+GEO') '''Property containing the mapping between the crease pattern nodes with duplicate nodes and pattern with compressed nodes array. The criterion for removing a node is geometric, the threshold is specified in node_match_threshold. ''' def _get_unique_node_map(self): # reshape the coordinates in array of segments to the shape (n_N, n_D x_0 = self.X_cells_Ia # construct distance vectors between every pair of nodes x_x_0 = x_0[:, np.newaxis, :] - x_0[np.newaxis, :, :] # calculate the distance between every pair of nodes dist_0 = np.sqrt(np.einsum('...i,...i', x_x_0, x_x_0)) # identify those at the same location zero_dist = dist_0 < self.node_match_threshold # get their indices i_idx, j_idx = np.where(zero_dist) # take only the upper triangle indices upper_triangle = i_idx < j_idx idx_multi, idx_delete = i_idx[upper_triangle], j_idx[upper_triangle] # construct a boolean array with True at valid and False at deleted # indices idx_unique = np.ones((len(x_0), ), dtype='bool') idx_unique[idx_delete] = False # Boolean array of nodes to keep - includes both those that # are unique and redirection nodes to be substituted for duplicates idx_keep = np.ones((len(x_0), ), dtype=np.bool_) idx_keep[idx_delete] = False # prepare the enumeration map map ij_map = np.ones_like(dist_0, dtype=np.int_) + len(x_0) i_ = np.arange(len(x_0)) # indexes of nodes that are being kept idx_row = i_[idx_keep] # enumerate the kept nodes by putting their number onto the diagonal ij_map[idx_keep, idx_keep] = np.arange(len(idx_row)) # broadcast the substitution nodes into the interaction positions ij_map[i_idx, j_idx] = ij_map[i_idx, i_idx] # get the substitution node by picking up the minimum index within ac column idx_remap = np.min(ij_map, axis=0) return idx_unique, idx_remap I_CDij = tr.Property(depends_on='+GEO') @tr.cached_property def _get_I_CDij(self): n_cells, n_ic, n_id, _, x_cell_idx, _, y_cell_idx = self.cell_map x_idx, y_idx = x_cell_idx / 2, y_cell_idx / 2 n_x_, n_y_ = len(x_idx), len(y_idx) I_cell_offset = (n_ic + np.arange(n_x_ * n_y_).reshape( n_x_, n_y_)) * self.wb_cell.n_I I_CDij_map = (I_cell_offset.T[:, :, np.newaxis, np.newaxis] + self.wb_cell.I_boundary[np.newaxis, np.newaxis, :, :]) return I_CDij_map def setup_plot(self, pb): self.pb = pb X_Ia = self.X_Ia.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) I_M = self.I_CDij[(0, -1), :, (0, -1), :] _, idx_remap = self.unique_node_map J_M = idx_remap[I_M] X_Ma = X_Ia[J_M.flatten()] k3d_mesh = k3d.mesh(X_Ia, I_Fi, color=0x999999, side='double') pb.objects['k3d_mesh'] = k3d_mesh pb.plot_fig += k3d_mesh if self.show_nodes: self._add_nodes_to_fig(pb, X_Ma) if self.wb_cell.show_node_labels: self._add_nodes_labels_to_fig(pb, X_Ia) if self.show_wireframe: self._add_wireframe_to_fig(pb, X_Ia, I_Fi) def update_plot(self, pb): X_Ia = self.X_Ia.astype(np.float32) I_Fi = self.I_Fi.astype(np.uint32) I_M = self.I_CDij[(0, -1), :, (0, -1), :] _, idx_remap = self.unique_node_map J_M = idx_remap[I_M] X_Ma = X_Ia[J_M.flatten()] mesh = pb.objects['k3d_mesh'] mesh.vertices = X_Ia mesh.indices = I_Fi if self.show_nodes: if self.NODES in pb.objects: pb.objects[self.NODES].positions = X_Ma else: self._add_nodes_to_fig(pb, X_Ma) else: if self.NODES in pb.objects: pb.clear_object(self.NODES) if self.show_wireframe: if self.WIREFRAME in pb.objects: wireframe = pb.objects[self.WIREFRAME] wireframe.vertices = X_Ia wireframe.indices = I_Fi else: self._add_wireframe_to_fig(pb, X_Ia, I_Fi) else: if self.WIREFRAME in pb.objects: pb.clear_object(self.WIREFRAME) if self.show_node_labels: if self.NODES_LABELS in pb.objects: pb.clear_object(self.NODES_LABELS) self._add_nodes_labels_to_fig(pb, X_Ia) else: if self.NODES_LABELS in pb.objects: pb.clear_object(self.NODES_LABELS) def _add_nodes_labels_to_fig(self, pb, X_Ia): text_list = [] for I, X_a in enumerate(X_Ia): k3d_text = k3d.text('%g' % I, tuple(X_a), label_box=False, size=0.8, color=0x00FF00) pb.plot_fig += k3d_text text_list.append(k3d_text) pb.objects[self.NODES_LABELS] = text_list def _add_wireframe_to_fig(self, pb, X_Ia, I_Fi): k3d_mesh_wireframe = k3d.mesh(X_Ia, I_Fi, color=0x000000, wireframe=True) pb.plot_fig += k3d_mesh_wireframe pb.objects[self.WIREFRAME] = k3d_mesh_wireframe def _add_nodes_to_fig(self, pb, X_Ma): k3d_points = k3d.points(X_Ma, point_size=300) pb.objects[self.NODES] = k3d_points pb.plot_fig += k3d_points def _show_or_hide_fig_object(self, pb, show_obj, obj_name, obj_add_fun, obj_update_fun): if show_obj: if obj_name in pb.objects: obj_update_fun() else: obj_add_fun() else: if obj_name in pb.objects: pb.clear_object(obj_name) def export_fold_file(self, path=None): # See https://github.com/edemaine/fold/blob/master/doc/spec.md for fold file specification # Viewer: https://edemaine.github.io/fold/examples/foldviewer.html output_data = { "file_spec": 1, "file_creator": "BMCS software suite", "file_author": "RWTH Aachen - Institute of Structural Concrete", "file_title": "Preliminary Base", "file_classes": ["singleModel"], "frame_title": "Preliminary Base Crease Pattern", "frame_classes": ["creasePattern"], "vertices_coords": self.X_Ia.tolist(), "faces_vertices": self.I_Fi.tolist(), # To be completed } if path is None: path = time.strftime("%Y%m%d-%H%M%S") + '-shell.fold' with open(path, 'w') as outfile: json.dump(output_data, outfile, sort_keys=True, indent=4)
class EnergyDissipation(bu.InteractiveModel): name='Energy' colors = dict( # color associations stored_energy = 'darkgreen', # recoverable free_energy_kin = 'darkcyan', # freedom - sky free_energy_iso = 'darkslateblue', # freedom - sky plastic_diss_s = 'darkorange', # fire - heat plastic_diss_w = 'red', # fire - heat damage_diss_s = 'darkgray', # ruined damage_diss_w = 'black' # ruined ) slider_exp = tr.WeakRef(bu.InteractiveModel) t_arr = tr.DelegatesTo('slider_exp') Sig_arr = tr.DelegatesTo('slider_exp') Eps_arr = tr.DelegatesTo('slider_exp') s_x_t = tr.DelegatesTo('slider_exp') s_y_t = tr.DelegatesTo('slider_exp') w_t = tr.DelegatesTo('slider_exp') iter_t = tr.DelegatesTo('slider_exp') show_iter = bu.Bool(False) E_plastic_work = bu.Bool(False) E_iso_free_energy = bu.Bool(True) E_kin_free_energy = bu.Bool(True) E_plastic_diss = bu.Bool(True) E_damage_diss = bu.Bool(True) ipw_view = bu.View( bu.Item('show_iter'), bu.Item('E_damage_diss'), bu.Item('E_plastic_work'), bu.Item('E_iso_free_energy'), bu.Item('E_kin_free_energy'), bu.Item('E_plastic_diss'), ) WUG_t = tr.Property def _get_W_t(self): W_arr = ( cumtrapz(self.Sig_arr[:, 0], self.s_x_t, initial=0) + cumtrapz(self.Sig_arr[:, 1], self.s_y_t, initial=0) + cumtrapz(self.Sig_arr[:, 2], self.w_t, initial=0) ) s_x_el_t = (self.s_x_t - self.Eps_arr[:, 0]) s_y_el_t = (self.s_y_t - self.Eps_arr[:, 1]) w_el_t = (self.w_t - self.Eps_arr[:, 2]) U_arr = ( self.Sig_arr[:, 0] * s_x_el_t / 2.0 + self.Sig_arr[:, 1] * s_y_el_t / 2.0 + self.Sig_arr[:, 2] * w_el_t / 2.0 ) G_arr = W_arr - U_arr return W_arr, U_arr, G_arr Eps = tr.Property """Energy dissipated in associatiation with individual internal variables """ def _get_Eps(self): Eps_names = self.slider_exp.slide_model.Eps_names E_i = cumtrapz(self.Sig_arr, self.Eps_arr, initial=0, axis=0) return SimpleNamespace(**{Eps_name: E for Eps_name, E in zip(Eps_names, E_i.T)}) mechanisms = tr.Property """Energy in association with mechanisms (damage and plastic dissipation) or free energy """ def _get_mechanisms(self): E_i = cumtrapz(self.Sig_arr, self.Eps_arr, initial=0, axis=0) E_T_x_pi_, E_T_y_pi_, E_N_pi_, E_z_, E_alpha_x_, E_alpha_y_, E_omega_T_, E_omega_N_ = E_i.T E_plastic_work_T = E_T_x_pi_ + E_T_y_pi_ E_plastic_work_N = E_N_pi_ E_plastic_work = E_plastic_work_T + E_plastic_work_N E_iso_free_energy = E_z_ E_kin_free_energy = E_alpha_x_ + E_alpha_y_ E_plastic_diss_T = E_plastic_work_T - E_iso_free_energy - E_kin_free_energy E_plastic_diss_N = E_plastic_work_N E_plastic_diss = E_plastic_diss_T + E_plastic_diss_N E_damage_diss = E_omega_T_ + E_omega_N_ return SimpleNamespace(**{'plastic_work_N': E_plastic_work_N, 'plastic_work_T': E_plastic_work_T, 'plastic_work': E_plastic_work, 'iso_free_energy': E_iso_free_energy, 'kin_free_energy': E_kin_free_energy, 'plastic_diss_N': E_plastic_diss_N, 'plastic_diss_T': E_plastic_diss_T, 'plastic_diss': E_plastic_diss, 'damage_diss_N': E_omega_N_, 'damage_diss_T': E_omega_T_, 'damage_diss': E_damage_diss}) def plot_energy(self, ax, ax_i): W_arr = ( cumtrapz(self.Sig_arr[:, 0], self.s_x_t, initial=0) + cumtrapz(self.Sig_arr[:, 1], self.s_y_t, initial=0) + cumtrapz(self.Sig_arr[:, 2], self.w_t, initial=0) ) s_x_el_t = (self.s_x_t - self.Eps_arr[:, 0]) s_y_el_t = (self.s_y_t - self.Eps_arr[:, 1]) w_el_t = (self.w_t - self.Eps_arr[:, 2]) U_arr = ( self.Sig_arr[:, 0] * s_x_el_t / 2.0 + self.Sig_arr[:, 1] * s_y_el_t / 2.0 + self.Sig_arr[:, 2] * w_el_t / 2.0 ) G_arr = W_arr - U_arr ax.plot(self.t_arr, W_arr, lw=0.5, color='black', label=r'$W$ - Input work') ax.plot(self.t_arr, G_arr, '--', color='black', lw = 0.5, label=r'$W^\mathrm{inel}$ - Inelastic work') ax.fill_between(self.t_arr, W_arr, G_arr, color=self.colors['stored_energy'], alpha=0.2) ax.set_xlabel('$t$ [-]'); ax.set_ylabel(r'$E$ [Nmm]') ax.legend() E_i = cumtrapz(self.Sig_arr, self.Eps_arr, initial=0, axis=0) E_T_x_pi_, E_T_y_pi_, E_N_pi_, E_z_, E_alpha_x_, E_alpha_y_, E_omega_T_, E_omega_N_ = E_i.T E_plastic_work_T = E_T_x_pi_ + E_T_y_pi_ E_plastic_work_N = E_N_pi_ E_plastic_work = E_plastic_work_T + E_plastic_work_N E_iso_free_energy = E_z_ E_kin_free_energy = E_alpha_x_ + E_alpha_y_ E_plastic_diss_T = E_plastic_work_T - E_iso_free_energy - E_kin_free_energy E_plastic_diss_N = E_plastic_work_N E_plastic_diss = E_plastic_diss_T + E_plastic_diss_N E_damage_diss = E_omega_T_ + E_omega_N_ E_level = 0 if self.E_damage_diss: ax.plot(self.t_arr, E_damage_diss + E_level, color='black', lw=1) ax_i.plot(self.t_arr, E_damage_diss, color='gray', lw=2, label=r'damage diss.: $Y\dot{\omega}$') ax.fill_between(self.t_arr, E_omega_N_ + E_level, E_level, color='black', hatch='|'); E_d_level = E_level + E_omega_N_ ax.fill_between(self.t_arr, E_omega_T_ + E_d_level, E_d_level, color='gray', alpha=0.3); E_level = E_damage_diss if self.E_plastic_work: ax.plot(self.t_arr, E_plastic_work + E_level, lw=0.5, color='black') # ax.fill_between(self.t_arr, E_plastic_work + E_level, E_level, color='red', alpha=0.3) label = r'plastic work: $\sigma \dot{\varepsilon}^\pi$' ax_i.plot(self.t_arr, E_plastic_work, color='red', lw=2,label=label) ax.fill_between(self.t_arr, E_plastic_work_N + E_level, E_level, color='orange', alpha=0.3); E_p_level = E_level + E_plastic_work_N ax.fill_between(self.t_arr, E_plastic_work_T + E_p_level, E_p_level, color='red', alpha=0.3); if self.E_plastic_diss: ax.plot(self.t_arr, E_plastic_diss + E_level, lw=.4, color='black') label = r'apparent pl. diss.: $\sigma \dot{\varepsilon}^\pi - X\dot{\alpha} - Z\dot{z}$' ax_i.plot(self.t_arr, E_plastic_diss, color='red', lw=2, label=label) ax.fill_between(self.t_arr, E_plastic_diss_N + E_level, E_level, color='red', hatch='-'); E_d_level = E_level + E_plastic_diss_N ax.fill_between(self.t_arr, E_plastic_diss_T + E_d_level, E_d_level, color='red', alpha=0.3); E_level += E_plastic_diss if self.E_iso_free_energy: ax.plot(self.t_arr, E_iso_free_energy + E_level, '-.', lw=0.5, color='black') ax.fill_between(self.t_arr, E_iso_free_energy + E_level, E_level, color='royalblue', hatch='|') ax_i.plot(self.t_arr, -E_iso_free_energy, '-.', color='royalblue', lw=2, label=r'iso. diss.: $Z\dot{z}$') E_level += E_iso_free_energy if self.E_kin_free_energy: ax.plot(self.t_arr, E_kin_free_energy + E_level, '-.', color='black', lw=0.5) ax.fill_between(self.t_arr, E_kin_free_energy + E_level, E_level, color='royalblue', alpha=0.2); ax_i.plot(self.t_arr, -E_kin_free_energy, '-.', color='blue', lw=2, label=r'free energy: $X\dot{\alpha}$') ax_i.legend() ax_i.set_xlabel('$t$ [-]'); ax_i.set_ylabel(r'$E$ [Nmm]') @staticmethod def subplots(fig): ax_work, ax_energies = fig.subplots(1, 2) ax_iter = ax_work.twinx() return ax_work, ax_energies, ax_iter def update_plot(self, axes): ax_work, ax_energies, ax_iter = axes self.plot_energy(ax_work, ax_energies) if self.show_iter: ax_iter.plot(self.t_arr, self.iter_t) ax_iter.set_ylabel(r'$n_\mathrm{iter}$') def xsubplots(self, fig): ((ax1, ax2), (ax3, ax4)) = fig.subplots(2, 2, figsize=(10, 5), tight_layout=True) ax11 = ax1.twinx() ax22 = ax2.twinx() ax33 = ax3.twinx() ax44 = ax4.twinx() return ax1, ax11, ax2, ax22, ax3, ax33, ax4, ax44 def xupdate_plot(self, axes): ax1, ax11, ax2, ax22, ax3, ax33, ax4, ax44 = axes self.get_response([6, 0, 0]) # plot_Sig_Eps(s_x_t, Sig_arr, Eps_arr, iter_t, *axes) s_x_pi_, s_y_pi_, w_pi_, z_, alpha_x_, alpha_y_, omega_s_, omega_w_ = self.Eps_arr.T tau_x_pi_, tau_y_pi_, sig_pi_, Z_, X_x_, X_y_, Y_s_, Y_w_ = self.Sig_arr.T ax1.plot(self.w_t, sig_pi_, color='green') ax11.plot(self.s_x_t, tau_x_pi_, color='red') ax2.plot(self.w_t, omega_w_, color='green') ax22.plot(self.w_t, omega_s_, color='red')