diff --git a/index.ipynb b/index.ipynb index 89aecdb41882b88a0f75052f16d271144f315a8a..386c97b050a9b443363e09e5a2c6976fa76ddc94 100644 --- a/index.ipynb +++ b/index.ipynb @@ -269,7 +269,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.8.8" }, "toc": { "base_numbering": 1, diff --git a/pull_out/2_1_1_PO_observation.ipynb b/pull_out/2_1_1_PO_observation.ipynb index 909124aad96daaf887e88b83be5a8e2a0a31b250..2400abdedb18dad49e0641d105638e9c0ecc11c7 100644 --- a/pull_out/2_1_1_PO_observation.ipynb +++ b/pull_out/2_1_1_PO_observation.ipynb @@ -178,7 +178,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.8.8" }, "toc": { "base_numbering": 1, diff --git a/pull_out/2_2_1_PO_configuration_explorer.ipynb b/pull_out/2_2_1_PO_configuration_explorer.ipynb index 7feacb69474c79dfc699008a656a832a6f910bdb..6380db65426b5bacc4f42402cd1c1c3f872c455c 100644 --- a/pull_out/2_2_1_PO_configuration_explorer.ipynb +++ b/pull_out/2_2_1_PO_configuration_explorer.ipynb @@ -280,7 +280,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.9.1" + "version": "3.8.8" }, "toc": { "base_numbering": 1, diff --git a/pull_out/fragmentation.ipynb b/pull_out/fragmentation.ipynb index d35f1d7d03f840cb86b246ba2b951cccf42f730a..713e7ffe99814b52c16b2ed6b2234ce6ee640837 100644 --- a/pull_out/fragmentation.ipynb +++ b/pull_out/fragmentation.ipynb @@ -47,14 +47,14 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": 4, "id": "approved-pressing", "metadata": {}, "outputs": [ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "e338f59bf98748459c9275d04c2a6922", + "model_id": "42a4e4377cd5488f8e840614099ea927", "version_major": 2, "version_minor": 0 }, @@ -83,7 +83,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 2, "id": "olympic-validity", "metadata": {}, "outputs": [ @@ -96,7 +96,7 @@ "E_\\mathrm{m}*Piecewise((P/(A_\\mathrm{f}*E_\\mathrm{f} + A_\\mathrm{m}*E_\\mathrm{m}), x <= -A_\\mathrm{m}*E_\\mathrm{m}*P/(A_\\mathrm{f}*E_\\mathrm{f}*\\bar{\\tau}*p + A_\\mathrm{m}*E_\\mathrm{m}*\\bar{\\tau}*p)), (-\\bar{\\tau}*p*x/(A_\\mathrm{m}*E_\\mathrm{m}), True))" ] }, - "execution_count": 16, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -238,7 +238,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 3, "id": "enabling-engine", "metadata": {}, "outputs": [], @@ -252,7 +252,7 @@ }, { "cell_type": "markdown", - "id": "interior-recycling", + "id": "0f66e3d8-e60d-4ff1-9a43-32ed2a486d7f", "metadata": {}, "source": [ "The characteristic points of the ACK model deliver the values " @@ -260,7 +260,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "sunset-trading", "metadata": {}, "outputs": [], @@ -292,7 +292,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6991363d3cc54733a4bf1d2e23c5cfe0", + "model_id": "ed30c8a8b1094c36b36199f212a8b434", "version_major": 2, "version_minor": 0 }, @@ -327,7 +327,7 @@ }, { "cell_type": "code", - "execution_count": 12, + "execution_count": 2, "id": "boxed-scott", "metadata": { "tags": [] @@ -340,7 +340,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 3, "id": "regulated-republic", "metadata": { "tags": [] @@ -352,7 +352,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 4, "id": "subsequent-coach", "metadata": { "tags": [] @@ -361,7 +361,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "eccd60851ba7434cadb9ad344207ccec", + "model_id": "63ba009ff6ac49c080b258484966fe0a", "version_major": 2, "version_minor": 0 }, @@ -393,10 +393,81 @@ "- Given the cross sectional areas $A_\\mathrm{m}, A_\\mathrm{f}$, concrete and reinforcement stiffness $E_\\mathrm{m}, E_\\mathrm{f}$ and strength $\\sigma_\\mathrm{mu}, \\sigma_\\mathrm{fu}$, reinforcement ratio $V_\\mathrm{f}$, bond intensity $T$, calculate the average crack width at failure of a tensile specimen." ] }, + { + "cell_type": "code", + "execution_count": 3, + "id": "26acec03-96b0-4f31-b890-4e6727d40060", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "ph.interact()" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "694c155c-bc53-4750-82ef-a9b0b9f0a65d", + "metadata": {}, + "outputs": [], + "source": [ + "%matplotlib widget\n", + "import pmcm.pmcm2" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "0d586905-6bf3-4f21-8a29-81973da643ff", + "metadata": {}, + "outputs": [], + "source": [ + "pm = pmcm.pmcm2.PMCM()" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "11339d52-6dc2-49e0-9c60-7bf01b8d2b4b", + "metadata": {}, + "outputs": [ + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "137932dce7424c4091100ca25ef81d0b", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "VBox(children=(HBox(children=(VBox(children=(Tree(layout=Layout(align_items='stretch', border='solid 1px black…" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "pm.interact()" + ] + }, { "cell_type": "code", "execution_count": null, - "id": "second-entry", + "id": "be17e48c-81b5-493b-8e14-6f7631019f82", "metadata": {}, "outputs": [], "source": [] diff --git a/pull_out/pmcm/__init__.py b/pull_out/pmcm/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/pull_out/pmcm/api.py b/pull_out/pmcm/api.py new file mode 100644 index 0000000000000000000000000000000000000000..c183405df21793738ccf3d0088b5f01d054b588a --- /dev/null +++ b/pull_out/pmcm/api.py @@ -0,0 +1 @@ +from .pmcm import PMCM \ No newline at end of file diff --git a/pull_out/pmcm/pmcm.py b/pull_out/pmcm/pmcm.py new file mode 100644 index 0000000000000000000000000000000000000000..be5586caf4e71d372f3efdf6cfd7b08e17a87cab --- /dev/null +++ b/pull_out/pmcm/pmcm.py @@ -0,0 +1,276 @@ +""" +Probabilistic multiple cracking model +""" +from typing import List, Any, Union +import bmcs_utils.api as bu + +import traits.api as tr +import numpy as np +import warnings + +from bmcs_utils.trait_types import Float +from scipy.optimize import newton + +warnings.filterwarnings("error", category=RuntimeWarning) + + +class CrackBridgeModel(bu.Model): + """ + Record of all material parameters of the composite. The model components + (PullOutModel, CrackBridgeRespSurf, PMCM) are all linked to the database record + and access the parameters they require. Some parameters are shared across all + three components (Em, Ef, vf), some are specific to a particular type of the + PulloutModel. + """ + +class CBMConstantBond(CrackBridgeModel): + """ + Return the matrix stress profile of a crack bridge for a given control slip + at the loaded end + """ + Em = bu.Float(28000) + Ef = bu.Float(180000) + vf = bu.Float(0.01) + T = bu.Float(8) + + ipw_view = bu.View( + bu.Item('Em'), + bu.Item('Ef'), + bu.Item('vf'), + bu.Item('T'), + ) + + @property + def Ec(self): + return self.Em * (1 - self.vf) + self.Ef * self.vf # [MPa] mixture rule + +class CrackBridgeRespSurface(bu.Model): + """ + Crack bridge response surface that returns the values of matrix stress + along ahead of a crack and crack opening for a specified remote stress + and boundary conditions. + """ + cb = bu.Instance(CrackBridgeModel) + + def get_sig_m(self, z, sig_c): + """Get the profile of matrix stress along the specimen + :param z: np.ndarray + :type sig_c: float + """ + cb = self.cb + sig_m = np.minimum(z * cb.T * cb.vf / (1 - cb.vf), cb.Em * sig_c / + (cb.vf * cb.Ef + (1 - cb.vf) * cb.Em)) + return sig_m + + def get_eps_f(self, z, sig_c): + cb = self.cb + sig_m = self.get_sig_m(sig_c, z ) + eps_f = (sig_c - sig_m * (1 - cb.vf)) / cb.vf / cb.Ef + return eps_f + + sig_c_slider: float = bu.Float(1.0, BC=True) + + ipw_view = bu.View( + bu.Item('sig_c_slider') + ) + + @staticmethod + def subplots(fig): + ax = fig.subplots(1,1) + ax1 = ax.twinx() + return ax, ax1 + + def update_plot(self, axes): + ax, ax1 = axes + x_range = np.linspace(-1000,1000,1000) + z_range = np.abs(x_range) + + sig_m_range = self.get_sig_m(self.sig_c_slider, z_range) + eps_f_range = self.get_eps_f(self.sig_c_slider, z_range) + sig_max = np.max(sig_m_range) + eps_max = np.max(eps_f_range) + + ax.plot(x_range, sig_m_range, color='black') + ax.fill_between(x_range, sig_m_range, color='gray', alpha=0.1) + ax.set_ylim(ymin=-0.03*sig_max) + ax1.plot(x_range, eps_f_range, color='blue') + ax1.fill_between(x_range, eps_f_range, color='blue', alpha=0.1) + ax1.set_ylim(ymin=-0.03*eps_max) + +class PMCMHist(bu.Model): + pmcm = tr.WeakRef + + K = bu.Int(0) + t = bu.Float(0) + K_max = tr.Property(depends_on='state_changed') + @tr.cached_property + def _get_K_max(self): + sig_c_K, eps_c_K, sig_mu_x, x, CS, sig_m_x_K, sig_m_x_K1 = self.pmcm.cracking_history + K_max = len(sig_c_K) + # ceil the index of current crack + self.K = np.min([self.K, K_max]) + return K_max + + ipw_view = bu.View( + bu.Item('K', latex=r'\mathrm{state}', readonly=True), + time_editor=bu.HistoryEditor( + var='t', + var_max='K_max' + ) + ) + + @staticmethod + def subplots(fig): + ax1, ax2 = fig.subplots(1,2) + ax11 = ax1.twinx() + return ax1, ax11, ax2 + + def update_plot(self, axes): + ax, ax_cs, ax_sig_x = axes + cr = int(self.t * (self.K_max-2)) + self.K = cr + self.pmcm.plot(axes) + sig_c_K, eps_c_K, sig_mu_x, x, CS, sig_m_x_K, sig_m_x_K1 = self.pmcm.cracking_history + ax_sig_x.plot(x,sig_m_x_K[cr]) + ax_sig_x.plot(x,sig_m_x_K1[cr], linestyle='dashed') + ax.plot(eps_c_K[cr],sig_c_K[cr],color='magenta',marker='o') + + +class PMCM(bu.Model): + name = "PMCM" + """ + Implement the global crack tracing algorithm based on a crack bridge response surface + """ + + history = bu.Instance(PMCMHist) + def _history_default(self): + return PMCMHist(pmcm=self) + + cb = bu.Instance(CrackBridgeModel) + def _cb_default(self): + return CBMConstantBond() + + cb_rs = tr.Property#(depends_on="state_changed") + @tr.cached_property + def _get_cb_rs(self): + return CrackBridgeRespSurface(cb=self.cb) + + tree = ['cb', 'cb_rs', 'history'] + + n_x = bu.Int(5000, ALG=True) + L_x = bu.Float(500, GEO=True) + sig_cu = bu.Float(20, MAT=True) + sig_mu = bu.Float(10, MAT=True) + m = bu.Float(4, MAT=True) + + ipw_view = bu.View( + bu.Item('n_x'), + bu.Item('L_x'), + bu.Item('sig_cu'), + bu.Item('sig_mu'), + bu.Item('m'), + ) + + def get_z_x(self, x, XK): # distance to the closest crack (*\label{get_z_x}*) + """Specimen discretization + """ + z_grid = np.abs(x[:, np.newaxis] - np.array(XK)[np.newaxis, :]) + return np.amin(z_grid, axis=1) + + def get_sig_c_z(self, sig_mu, z, sig_c_pre): + """ + :param sig_c_pre: + :type sig_mu: float + """ + # crack initiating load at a material element + #print('sig_mu', sig_mu) + fun = lambda sig_c: sig_mu - self.cb_rs.get_sig_m(sig_c, z) + try: # search for the local crack load level + sig_c = newton(fun, sig_c_pre) + #print('sig_c', sig_c) + return sig_c + except (RuntimeWarning, RuntimeError): + # solution not found (shielded zone) return the ultimate composite strength + return self.sig_cu + + def get_sig_c_K(self, z_x, x, sig_c_pre, sig_mu_x): + # crack initiating loads over the whole specimen + get_sig_c_x = np.vectorize(self.get_sig_c_z) + sig_c_x = get_sig_c_x(sig_mu_x, z_x, sig_c_pre) + #print('sig_c_x', z_x, x, sig_c_pre, sig_mu_x) + #print('sig_c_x', sig_c_x) + y_idx = np.argmin(sig_c_x) + return sig_c_x[y_idx], x[y_idx] + + cracking_history = tr.Property(depends_on='state_changed') + @tr.cached_property + def _get_cracking_history(self): + cb = self.cb + L_x, n_x, sig_mu, sig_cu, m = self.L_x, self.n_x, self.sig_mu, self.sig_cu, self.m + x = np.linspace(0, L_x, n_x) # specimen discretization + sig_mu_x: np.ndarray[np.float_] = sig_mu * np.random.weibull( + m, size=n_x) # matrix strength + + XK: List[float] = [] # recording the crack postions + sig_c_K: List[float] = [0.] # recording the crack initiating loads + eps_c_K: List[float] = [0.] # recording the composite strains + CS: List[float] = [L_x, L_x / 2] # initial crack spacing + sig_m_x_K1: List[float] = [np.zeros_like(x)] # stress profiles at crack states + sig_m_x_K: List[float] = [np.zeros_like(x)] # stress profiles after crack states + + + Ec: float = cb.Ec + Em: float = cb.Em + idx_0 = np.argmin(sig_mu_x) + XK.append(x[idx_0]) # position of the first crack + sig_c_0 = sig_mu_x[idx_0] * Ec / Em + sig_m_x_K1.append(np.ones_like(x)*sig_mu_x[idx_0]) # matrix stress + #print('sig_c_0', sig_c_0) + sig_c_K.append(sig_c_0) + eps_c_K.append(sig_mu_x[idx_0] / Em) + + while True: + z_x = self.get_z_x(x, XK) # distances to the nearest crack + sig_m_x_K.append(self.cb_rs.get_sig_m(sig_c_K[-1], z_x)) # matrix stress + sig_c_k, y_i = self.get_sig_c_K(z_x, x, sig_c_K[-1], sig_mu_x) # identify next crack + sig_m_x_K1.append(self.cb_rs.get_sig_m(sig_c_k, z_x)) # matrix stress + if sig_c_k == sig_cu: + break + XK.append(y_i) # record crack position + sig_c_K.append(sig_c_k) # corresponding composite stress + eps_c_K.append( # composite strain - integrate the strain field + np.trapz(self.cb_rs.get_eps_f(sig_c_k, self.get_z_x(x, XK)), x) / np.amax(x)) + XK_arr = np.hstack([[0], np.sort(np.array(XK)), [L_x]]) + CS.append(np.average(XK_arr[1:] - XK_arr[:-1])) # crack spacing + + sig_c_K.append(sig_cu) # the ultimate state + eps_c_K.append(np.trapz(self.cb_rs.get_eps_f(sig_cu, self.get_z_x(x, XK) ), x) / np.amax(x)) + CS.append(CS[-1]) + return np.array(sig_c_K), np.array(eps_c_K), sig_mu_x, x, np.array(CS), np.array(sig_m_x_K), np.array(sig_m_x_K1) + + @staticmethod + def subplots(fig): + ax1, ax2 = fig.subplots(1,2) + ax11 = ax1.twinx() + return ax1, ax11, ax2 + + def plot(self, axes): + ax, ax_cs, ax_sig_x = axes + sig_c_K, eps_c_K, sig_mu_x, x, CS, sig_m_x_K, sig_m_x_K1 = self.cracking_history + n_c = len(eps_c_K) - 2 # numer of cracks + ax.plot(eps_c_K, sig_c_K, marker='o', label='%d cracks:' % n_c) + ax.set_xlabel(r'$\varepsilon_\mathrm{c}$ [-]'); + ax.set_ylabel(r'$\sigma_\mathrm{c}$ [MPa]') + ax_sig_x.plot(x, sig_mu_x, color='orange') + ax_sig_x.fill_between(x, sig_mu_x, 0, color='orange', alpha=0.1) + ax_sig_x.set_xlabel(r'$x$ [mm]'); + ax_sig_x.set_ylabel(r'$\sigma$ [MPa]') + ax.legend() + eps_c_KK = np.array([eps_c_K[:-1], eps_c_K[1:]]).T.flatten() + CS_KK = np.array([CS[:-1], CS[:-1]]).T.flatten() + ax_cs.plot(eps_c_KK, CS_KK, color='gray') + ax_cs.fill_between(eps_c_KK, CS_KK, color='gray', alpha=0.2) + ax_cs.set_ylabel(r'$\ell_\mathrm{cs}$ [mm]'); + + def update_plot(self, axes): + self.plot(axes) diff --git a/pull_out/pmcm/pmcm2.py b/pull_out/pmcm/pmcm2.py new file mode 100644 index 0000000000000000000000000000000000000000..6c34898bde6d54d0f1397d91badec5dea1bc47d4 --- /dev/null +++ b/pull_out/pmcm/pmcm2.py @@ -0,0 +1,344 @@ +# %% md + +import numpy as np +from scipy.optimize import newton +import matplotlib.pylab as plt +import traits.api as tr +import bmcs_utils.api as bu + +# %% +import warnings # (*\label{error1}*) + +warnings.filterwarnings("error", category=RuntimeWarning) # (*\label{error2}*) + + +class CrackBridgeModel(bu.Model): + """ + Record of all material parameters of the composite. The model components + (PullOutModel, CrackBridgeRespSurf, PMCM) are all linked to the database record + and access the parameters they require. Some parameters are shared across all + three components (Em, Ef, vf), some are specific to a particular type of the + PulloutModel. + """ + + +class CBMConstantBond(CrackBridgeModel): + """ + Return the matrix stress profile of a crack bridge for a given control slip + at the loaded end + """ + Em = bu.Float(25e3, MAT=True) # [MPa] matrix modulus + Ef = bu.Float(180e3, MAT=True) # [MPa] fiber modulus + vf = bu.Float(0.01, CS=True) # [-] reinforcement ratio + T = bu.Float(12., MAT=True) # [N/mm^3] bond intensity + + ipw_view = bu.View( + bu.Item('Em'), + bu.Item('Ef'), + bu.Item('vf'), + bu.Item('T'), + ) + +class CrackBridgeRespSurface(bu.Model): + + cb = bu.Instance(CrackBridgeModel) + + ## Crack bridge with constant bond + def get_sig_m(self, z, sig_c): # matrix stress (*\label{sig_m}*) + T = self.cb.T + vf = self.cb.vf + Em = self.cb.Em + Ef = self.cb.Ef + sig_m = np.minimum(z * T * vf / (1 - vf), Em * sig_c / (vf * Ef + (1 - vf) * Em)) + return sig_m + + def get_eps_f(self, z, sig_c): # reinforcement strain (*\label{sig_f}*) + vf = self.cb.vf + Ef = self.cb.Ef + sig_m = self.get_sig_m(z, sig_c) + eps_f = (sig_c - sig_m * (1 - vf)) / vf / Ef + return eps_f + + sig_c_slider: float = bu.Float(1.0, BC=True) + + ipw_view = bu.View( + bu.Item('sig_c_slider') + ) + + @staticmethod + def subplots(fig): + ax = fig.subplots(1,1) + ax1 = ax.twinx() + return ax, ax1 + + def update_plot(self, axes): + ax, ax1 = axes + x_range = np.linspace(-300,300,1000) + z_range = np.abs(x_range) + + sig_m_range = self.get_sig_m(z_range, self.sig_c_slider) + eps_f_range = self.get_eps_f(z_range, self.sig_c_slider) + sig_max = np.max(sig_m_range) + eps_max = np.max(eps_f_range) + + ax.plot(x_range, sig_m_range, color='black') + ax.fill_between(x_range, sig_m_range, color='gray', alpha=0.1) + ax.set_ylim(ymin=-0.03*sig_max) + ax1.plot(x_range, eps_f_range, color='blue') + ax1.fill_between(x_range, eps_f_range, color='blue', alpha=0.1) + ax1.set_ylim(ymin=-0.03*eps_max) + + +class PMCM(bu.Model): + name='Fragmentation' + sig_cu = bu.Float(10.0, MAT=True) # [MPa] composite strength + sig_mu = bu.Float(3.0, MAT=True) # [MPa] matrix strength + m = bu.Float(4, MAT=True) # Weibull shape modulus + ## Crack tracing algorithm + n_x = bu.Int(200, MAT=True) + L_x = bu.Float(380, MAT=True) + + cb = bu.Instance(CrackBridgeModel) + def _cb_default(self): + return CBMConstantBond() + + cb_rs = tr.Property + @tr.cached_property + def _get_cb_rs(self): + return CrackBridgeRespSurface(cb=self.cb) + # + # # %% + # + # cb_rs = bu.Instance(CrackBridgeRespSurface, ()) + + tree = ['cb', 'cb_rs'] + + ipw_view = bu.View( + bu.Item('n_x'), + bu.Item('L_x'), + bu.Item('sig_cu'), + bu.Item('sig_mu'), + bu.Item('m'), + ) + + + ## Specimen discretization + def get_z_x(self, x, XK): # distance to the closest crack (*\label{get_z_x}*) + z_grid = np.abs(x[:, np.newaxis] - np.array(XK)[np.newaxis, :]) + return np.amin(z_grid, axis=1) + + def get_sig_c_z(self, sig_mu, z, sig_c_pre): + # crack initiating load at a material element + fun = lambda sig_c: sig_mu - self.cb_rs.get_sig_m(z, sig_c) + try: # search for the local crack load level + return newton(fun, sig_c_pre) + except (RuntimeWarning, RuntimeError): + # solution not found (shielded zone) return the ultimate composite strength + return self.sig_cu + + def get_sig_c_K(self, z_x, x, sig_c_pre, sig_mu_x): + # crack initiating loads over the whole specimen + get_sig_c_x = np.vectorize(self.get_sig_c_z) + sig_c_x = get_sig_c_x(sig_mu_x, z_x, sig_c_pre) + y_idx = np.argmin(sig_c_x) + return sig_c_x[y_idx], x[y_idx] + + cracking_history = tr.Property(depends_on='state_changed') + @tr.cached_property + def _get_cracking_history(self): + return self.get_cracking_history() + + def get_cracking_history(self, update_progress=None): + L_x, n_x = self.L_x, self.n_x + sig_mu = self.sig_mu + m = self.m + x = np.linspace(0, L_x, n_x) # specimen discretization (*\label{discrete}*) + sig_mu_x = sig_mu * np.random.weibull(m, size=n_x) # matrix strength (*\label{m_strength}*) + + vf = self.cb_rs.cb.vf + Em = self.cb_rs.cb.Em + Ef = self.cb_rs.cb.Ef + + Ec = Em * (1 - vf) + Ef * vf # [MPa] mixture rule + + XK = [] # recording the crack postions + sig_c_K = [0.] # recording the crack initating loads + eps_c_K = [0.] # recording the composite strains + CS = [L_x, L_x / 2] # crack spacing + sig_m_x_K = [np.zeros_like(x)] # stress profiles for crack states + + idx_0 = np.argmin(sig_mu_x) + XK.append(x[idx_0]) # position of the first crack + sig_c_0 = sig_mu_x[idx_0] * Ec / Em + sig_c_K.append(sig_c_0) + eps_c_K.append(sig_mu_x[idx_0] / Em) + + while True: + z_x = self.get_z_x(x, XK) # distances to the nearest crack + sig_m_x_K.append(self.cb_rs.get_sig_m(z_x, sig_c_K[-1])) # matrix stress + sig_c_k, y_i = self.get_sig_c_K(z_x, x, sig_c_K[-1], sig_mu_x) # identify next crack + if sig_c_k == self.sig_cu: # (*\label{no_crack}*) + break + if update_progress: # callback to user interface + update_progress(sig_c_k) + XK.append(y_i) # record crack position + sig_c_K.append(sig_c_k) # corresponding composite stress + eps_c_K.append( # composite strain - integrate the strain field + np.trapz(self.cb_rs.get_eps_f(self.get_z_x(x, XK), sig_c_k), x) / np.amax(x)) # (*\label{imple_avg_strain}*) + XK_arr = np.hstack([[0], np.sort(np.array(XK)), [L_x]]) + CS.append(np.average(XK_arr[1:] - XK_arr[:-1])) # crack spacing + + sig_c_K.append(self.sig_cu) # the ultimate state + eps_c_K.append(np.trapz(self.cb_rs.get_eps_f(self.get_z_x(x, XK), self.sig_cu), x) / np.amax(x)) + CS.append(CS[-1]) + if update_progress: + update_progress(sig_c_k) + return np.array(sig_c_K), np.array(eps_c_K), sig_mu_x, x, np.array(CS), np.array(sig_m_x_K) + + @staticmethod + def subplots(fig): + ax1, ax2 = fig.subplots(1,2) + ax11 = ax1.twinx() + return ax1, ax11, ax2 + + def plot(self, axes): + ax, ax_cs, ax_sig_x = axes + sig_c_K, eps_c_K, sig_mu_x, x, CS, sig_m_x_K = self.cracking_history + #sig_c_K, eps_c_K, sig_mu_x, x, CS, sig_m_x_K, sig_m_x_K1 = self.cracking_history + n_c = len(eps_c_K) - 2 # numer of cracks + ax.plot(eps_c_K, sig_c_K, marker='o', label='%d cracks:' % n_c) + ax.set_xlabel(r'$\varepsilon_\mathrm{c}$ [-]'); + ax.set_ylabel(r'$\sigma_\mathrm{c}$ [MPa]') + ax_sig_x.plot(x, sig_mu_x, color='orange') + ax_sig_x.fill_between(x, sig_mu_x, 0, color='orange', alpha=0.1) + ax_sig_x.set_xlabel(r'$x$ [mm]'); + ax_sig_x.set_ylabel(r'$\sigma$ [MPa]') + ax.legend() + eps_c_KK = np.array([eps_c_K[:-1], eps_c_K[1:]]).T.flatten() + CS_KK = np.array([CS[:-1], CS[:-1]]).T.flatten() + ax_cs.plot(eps_c_KK, CS_KK, color='gray') + ax_cs.fill_between(eps_c_KK, CS_KK, color='gray', alpha=0.2) + ax_cs.set_ylabel(r'$\ell_\mathrm{cs}$ [mm]'); + + def update_plot(self, axes): + self.plot(axes) + +# %% + +def render_widgets(): + ## Interactive application + import ipywidgets as ipw + + n_steps = 20 + margs_sliders = { + name: ipw.FloatSlider(description=desc, value=val, + min=minval, max=maxval, step=(maxval - minval) / n_steps, + continuous_update=False) + for name, desc, val, minval, maxval in [ + ('Em', r'\(E_\mathrm{m}\;\)[MPa]', 28e3, 1e3, 50e3), + ('Ef', r'\(E_\mathrm{f}\;\)[MPa]', 180e3, 1e3, 250e3), + ('vf', r'\(V_\mathrm{f}\;\)[-]', 0.01, 0.00001, 0.4), + ('T', r'\(T\;\)[N/mm$^3$]', 8, 0.0001, 20), + ('sig_cu', r'\(\sigma_\mathrm{cu}\;\)[MPa]', 10, 3, 100), + ('sig_mu', r'\(\sigma_\mathrm{mu}\;\)[MPa]', 5.0, 1, 10), + ('m', r'\(m\;\)[-]', 4, 0.8, 100), + ('L_x', r'\(L\;\)[mm]', 500, 200, 2000) + ] + } + pmcm_args = ['sig_cu', 'sig_mu', 'm', 'L_x', 'n_x'] + cb_args = ['Em', 'Ef', 'vf', 'T'] + + margs_sliders['n_x'] = ipw.IntSlider(description='$n_\mathrm{points}$', value=200, + min=20, max=1000, step=10, continuous_update=False) + crack_slider = ipw.IntSlider(description='crack', value=0, min=0, max=1, step=1) + progress = ipw.FloatProgress(min=0, max=1) # instantiate the bar + + fig, (ax, ax_sig_x) = plt.subplots(1, 2, figsize=(8, 3), tight_layout=True) + ax_cs = ax.twinx() + + + def update_progress(sig): + progress.value = sig + + + pmcm = PMCM() + + def init(): + for key, sl in margs_sliders.items(): + globals()[key] = sl.value + sig_c_K, eps_c_K, sig_mu_x, x, CS, sig_m_x_K = pmcm.get_cracking_history(update_progress) # (*\label{calc_curve}*) + progress.max = margs_sliders['sig_cu'].value + ax.plot(eps_c_K, sig_c_K, marker='o') # (*\label{show_curve1}*) + ax_sig_x.plot(x, sig_mu_x, color='red') + + + current_sig_m_x_K = [] + current_x = [] + + + def reset_crack_slider(x, sig_m_x_K): + global current_sig_m_x_K, current_x, sig_m_line, sig_eps_marker + current_sig_m_x_K = sig_m_x_K + current_x = x + n_cracks = len(sig_m_x_K) + crack_slider.max = n_cracks - 1 + crack_slider.value = 0 + sig_m_line, = ax_sig_x.plot(x, sig_m_x_K[0]) + sig_eps_marker, = ax.plot([0], [0], color='magenta', marker='o') + + + def update_crack_slider(crack): + global sig_m_line, sig_eps_marker + global sig_c_K, eps_c_K + if len(current_sig_m_x_K) > 0: + sig_m_line.set_ydata(current_sig_m_x_K[crack]) + sig_eps_marker.set_data(eps_c_K[crack], sig_c_K[crack]) + + + def update(**mparams): + global sig_c_K, eps_c_K + for key, val in mparams.items(): + globals()[key] = val + ax.clear() + ax_cs.clear() + ax_sig_x.clear() + ax.set_title('stress-strain and crack spacing') + ax_sig_x.set_title('matrix strength and stress profiles') + sig_c_K, eps_c_K, sig_mu_x, x, CS, sig_m_x_K = pmcm.get_cracking_history(update_progress) # (*\label{calc_curve}*) + n_c = len(eps_c_K) - 2 # numer of cracks + ax.plot(eps_c_K, sig_c_K, marker='o', label='%d cracks:' % n_c) # (*\label{show_curve1}*) + ax.set_xlabel(r'composite strain $\varepsilon_\mathrm{c}$ [-]'); + ax.set_ylabel(r'composite stress $\sigma_\mathrm{c}$ [MPa]') + ax_sig_x.plot(x, sig_mu_x, color='orange') + ax_sig_x.fill_between(x, sig_mu_x, 0, color='orange', alpha=0.1) + ax_sig_x.set_xlabel(r'$x$ [mm]'); + ax_sig_x.set_ylabel(r'matrix stress $\sigma_\mathrm{m}$ [MPa]') + ax.legend() + eps_c_KK = np.array([eps_c_K[:-1], eps_c_K[1:]]).T.flatten() + CS_KK = np.array([CS[:-1], CS[:-1]]).T.flatten() + ax_cs.plot(eps_c_KK, CS_KK, color='gray') + ax_cs.fill_between(eps_c_KK, CS_KK, color='gray', alpha=0.2) + ax_cs.set_ylabel(r'crack spacing $\ell_\mathrm{cs}$ [mm]'); + reset_crack_slider(x, sig_m_x_K) + + + def slider_layout(): + hbox_pr = ipw.HBox([progress]) + hbox = ipw.HBox([crack_slider]) + pmcm_layout = ipw.Layout(grid_template_columns='1fr') + pmcm_list = [ipw.Label('Crack tracing parameters')] + \ + [margs_sliders[arg] for arg in pmcm_args] + pmcm_col = ipw.GridBox(pmcm_list, layout=pmcm_layout) + cb_layout = ipw.Layout(grid_template_columns='1fr') + cb_list = [ipw.Label('Crack bridge parameters')] + \ + [margs_sliders[arg] for arg in cb_args] + cb_col = ipw.GridBox(cb_list, layout=cb_layout) + alg_params = ipw.HBox([pmcm_col, cb_col]) + box = ipw.VBox([hbox_pr, hbox, alg_params]) + display(box) + + + init() + slider_layout() + ipw.interactive_output(update_crack_slider, {'crack': crack_slider}) + ipw.interactive_output(update, margs_sliders);