Source code for opstool.analysis._sec_analysis

import matplotlib.pyplot as plt
import numpy as np
import openseespy.opensees as ops
from typing import Union

from ._smart_analyze import SmartAnalyze


[docs] class MomentCurvature: """Moment-Curvature Analysis for Fiber Section in OpenSeesPy. Parameters ---------- sec_tag : int, The previously defined section Tag. axial_force : float, optional Axial load, compression is negative, by default 0 """ def __init__(self, sec_tag: int, axial_force: float = 0) -> None: self.P = axial_force self.sec_tag = sec_tag self.phi, self.M, self.FiberData = None, None, None
[docs] def analyze( self, axis: str = "y", max_phi: float = 0.25, incr_phi: float = 1e-4, limit_peak_ratio: float = 0.8, smart_analyze: bool = True, debug: bool = False ): """Performing Moment-Curvature Analysis. Parameters ---------- axis : str, optional, "y" or "z" The axis of the section to be analyzed, by default "y". max_phi : float, optional The maximum curvature to analyze, by default 1.0. incr_phi : float, optional Curvature analysis increment, by default 1e-4. limit_peak_ratio : float, optional A ratio of the moment intensity after the peak used to stop the analysis., by default 0.8, i.e. a 20% drop after peak. smart_analyze : bool, optional Whether to use smart analysis options, by default True. debug: bool, optional Whether to use debug mode when smart analysis is True, by default False. .. note:: The termination of the analysis depends on whichever reaches `max_phi` or `post_peak_ratio` first. """ self.phi, self.M, self.FiberData = _analyze( sec_tag=self.sec_tag, P=self.P, axis=axis, max_phi=max_phi, incr_phi=incr_phi, stop_ratio=limit_peak_ratio, smart_analyze=smart_analyze, debug=debug )
[docs] def plot_M_phi(self, return_ax: bool = False): """Plot the moment-curvature relationship. Parameters ------------ return_ax: bool, default=False If True, return the axes for the plot of matplotlib. """ _, ax = plt.subplots(1, 1, figsize=(10, 10 * 0.618)) ax.plot(self.phi, self.M, lw=3, c="blue") ax.set_title( "$M-\\phi$", fontsize=28, ) ax.set_xlabel("$\\phi$", fontsize=25) ax.set_ylabel("$M$", fontsize=25) plt.xticks(fontsize=15) plt.yticks(fontsize=15) for loc in ["bottom", "left", "right", "top"]: ax.spines[loc].set_linewidth(1.0) plt.gcf().subplots_adjust(bottom=0.15) if return_ax: return ax else: plt.show()
[docs] def plot_fiber_responses( self, return_ax: bool = False ): """Plot the stress-strain histories of fiber by loc and matTag. Plot the fiber response of the material Tag ``mat`` closest to the ``loc``. Parameters ----------- return_ax: bool, default=False If True, return the axes for the plot of matplotlib. """ fiber_data = self.FiberData matTags = np.unique(fiber_data[-1][:, 3]) _, axs = plt.subplots(len(matTags), 1, figsize=(8, 3 * len(matTags))) for mat, ax in zip(matTags, axs): idxs = np.argwhere(np.abs(fiber_data[-1][:, 3] - mat) < 1e-6) strain = fiber_data[:, idxs, -1] stress = fiber_data[:, idxs, -2] for i in range(len(idxs)): ax.plot( strain[:, i, :], stress[:, i, :], lw=1, ) ax.set_title(f"matTag = {mat:.0f}", fontsize=15) ax.tick_params(labelsize=12) ax.set_ylabel("stress", fontsize=16) ax.set_xlabel("strain", fontsize=16) plt.subplots_adjust(wspace=0.02, hspace=0.4) plt.tight_layout() if return_ax: return axs else: plt.show()
[docs] def get_phi(self): """Get the curvature array. Returns ------- 1D array-like Curvature array. """ return self.phi
[docs] def get_curvature(self): """Get the curvature array. Returns ------- 1D array-like Curvature array. """ return self.get_phi()
[docs] def get_M(self): """Get the moment array. Returns ------- 1D array-like Moment array. """ return self.M
[docs] def get_moment(self): """Get the moment array. Returns ------- 1D array-like Moment array. """ return self.get_M()
[docs] def get_M_phi(self): """Get the moment and curvature array. Returns ------- (1D array-like, 1D array-like) (Curvature array, Moment array) """ return self.phi, self.M
[docs] def get_fiber_data(self): """All fiber data. Returns ------- Shape-(n,m,6) Array. n is the length of analysis steps, m is the fiber number, 6 contain ("yCoord", "zCoord", "area", 'mat', "stress", "strain") """ return self.FiberData
[docs] def get_limit_state( self, matTag: int = 1, threshold: float = 0, peak_drop: Union[float, bool] = False ): """Get the curvature and moment corresponding to a certain limit state. Parameters ---------- matTag : int, optional The OpenSeesPy material Tag used to determine the limit state., by default 1 threshold : float, optional The strain threshold used to determine the limit state by material `matTag`, by default 0 peak_drop : Union[float, bool], optional If True, A default 20% drop from the peak value of the moment will be used as the limit state; If float in [0, 1], this means that the ratio of ultimate strength to peak value is specified by this value, for example, peak_drop = 0.3, the ultimate strength = 0.7 * peak. `matTag` and `threshold` are not needed, by default False. Returns ------- (float, float) (Limit Curvature, Limit Moment) """ phi = self.phi M = self.M fiber_data = self.FiberData if peak_drop: if peak_drop is True: ratio_ = 0.8 else: ratio_ = 1 - peak_drop idx = np.argmax(M) au = np.argwhere(M[idx:] <= np.max(M) * ratio_) if au.size == 0: raise RuntimeError( f"Peak strength does not drop {1 - ratio_}, please increase target ductility ratio!" ) else: bu = np.min(au) + idx - 1 else: idxu = np.argwhere(np.abs(fiber_data[-1][:, 3] - matTag) < 1e-6) eu = threshold if eu >= 0: strain = np.array([np.max(data[idxu, -1]) for data in fiber_data]) au = np.argwhere(strain >= eu) else: strain = np.array([np.min(data[idxu, -1]) for data in fiber_data]) au = np.argwhere(strain < eu) if len(au) == 0: raise RuntimeError( "The ultimate strain is not reached, please increase target ductility ratio!" ) else: bu = np.min(au) Phi_u = phi[bu] M_u = M[bu] return Phi_u, M_u
[docs] def bilinearize(self, phiy: float, My: float, phiu: float, plot: bool = False, return_ax: bool = False): """Bilinear Approximation of Moment-Curvature Relation. Parameters ---------- phiy : float The initial yield curvature. My : float The initial yield moment. phiu : float The limit curvature. plot : bool, optional If True, plot the bilinear approximation, by default False. return_ax: bool, default=False If True, return the axes for the plot of matplotlib. Returns ------- (float, float) (Equivalent Curvature, Equivalent Moment) """ phi = self.phi M = self.M bu = np.argmin(np.abs(phiu - phi)) Q = np.trapz(M[: bu + 1], phi[: bu + 1]) k = My / phiy Phi_eq = (k * phiu - np.sqrt((k * phiu) ** 2 - 2 * k * Q)) / k M_eq = k * Phi_eq M_new = np.insert(M[0: bu + 1], 0, 0, axis=None) Phi_new = np.insert(phi[0: bu + 1], 0, 0, axis=None) if plot: _, ax = plt.subplots(1, 1, figsize=(10, 10 * 0.618)) ax.plot(Phi_new, M_new, lw=1.5, c="#2529d8") ax.plot([0, phiy, Phi_eq, phiu], [0, My, M_eq, M_eq], lw=1.5, c="#de0f17") ax.plot( phiy, My, "o", ms=12, mec="black", mfc="#0099e5", label="Initial Yield ($\\phi_y$,$M_y$)", ) ax.plot( Phi_eq, M_eq, "*", ms=15, mec="black", mfc="#ff4c4c", label="Equivalent Yield ($\\phi_{{eq}}$,$M_{{eq}}$)", ) maxy = np.max(ax.get_yticks()) ax.vlines(phiu, 0, maxy, colors="#34bf49", linestyles="dashed", lw=0.75) txt = ( f"$\\phi_y$={phiy:.3E}, $M_y$={My:.3E}\n" f"$\\phi_{{eq}}$={Phi_eq:.3E}, $M_{{eq}}$={M_eq:.3E}\n" f"$\\phi_{{u}}$={phiu:.3E}, $M_{{u}}$={M[bu]:.3E}" ) ax.text( 0.5, 0.4, txt, fontsize=15, ha="center", va="bottom", transform=ax.transAxes, ) ax.set_title( "Moment-Curvature", fontsize=22, ) ax.set_xlabel("$\\phi$", fontsize=20) ax.set_ylabel("$M$", fontsize=20) plt.xticks(fontsize=15) plt.yticks(fontsize=15) # ax.set_xlim(0, np.max(ax.get_xticks())) ax.set_ylim(0, maxy) for loc in ["bottom", "left", "right", "top"]: ax.spines[loc].set_linewidth(1.0) ax.legend(loc="lower center", fontsize=15) plt.gcf().subplots_adjust(bottom=0.15) if return_ax: return ax else: plt.show() return Phi_eq, M_eq
def _create_model(sec_tag): ops.model("basic", "-ndm", 3, "-ndf", 6) ops.node(1, 0.0, 0.0, 0.0) ops.node(2, 0.0, 0.0, 0.0) ops.fix(1, 1, 1, 1, 1, 1, 1) ops.fix(2, 0, 1, 1, 1, 0, 0) ops.element("zeroLengthSection", 1, 1, 2, sec_tag) def _create_axial_resp(p): ops.timeSeries("Constant", 1) ops.pattern("Plain", 1, 1) ops.load(2, p, 0, 0, 0, 0, 0) # nd FX, FY, Fz, MX, MY, MZ ops.wipeAnalysis() ops.system("SparseGeneral", "-piv") ops.constraints("Plain") ops.numberer("Plain") ops.test("NormDispIncr", 1.0e-12, 10, 3) ops.algorithm("Newton") ops.integrator("LoadControl", 1 / 10) ops.analysis("Static") ops.analyze(10) ops.loadConst("-time", 0.0) def _analyze( sec_tag, P=0.0, axis="y", max_phi=0.5, incr_phi=1e-5, stop_ratio=0.8, smart_analyze=True, debug: bool = False ): _create_model(sec_tag=sec_tag) if P != 0: _create_axial_resp(P) ops.timeSeries("Linear", 2) ops.pattern("Plain", 2, 2) if axis.lower() == "y": dof = 5 ops.load(2, 0, 0, 0, 0, 1, 0) elif axis.lower() == "z": dof = 6 ops.load(2, 0, 0, 0, 0, 0, 1) else: raise ValueError("Only supported axis = y or z!") M = [0] PHI = [0] FIBER_RESPONSES = [0] if smart_analyze: protocol = [max_phi] userControl = { "analysis": "Static", "testType": "NormDispIncr", "testTol": 1.0e-10, "tryAlterAlgoTypes": True, "algoTypes": [40, 30, 20], "relaxation": 0.5, "minStep": 1.0e-12, "printPer": 10000000000, "debugMode": debug, } analysis = SmartAnalyze(analysis_type="Static", **userControl) segs = analysis.static_split(protocol, maxStep=incr_phi) ii = 0 while True: seg = segs[ii] ii += 1 ok = analysis.StaticAnalyze(2, dof, seg) curr_M = ops.getLoadFactor(2) curr_Phi = ops.nodeDisp(2, dof) cond1 = np.abs(curr_M) < np.max(np.abs(M)) * (stop_ratio - 0.02) cond2 = np.abs(curr_Phi) >= max_phi PHI.append(ops.nodeDisp(2, dof)) M.append(curr_M) FIBER_RESPONSES.append(_get_fiber_sec_data(ele_tag=1)) if cond1 or cond2: break if ok < 0: raise RuntimeError("Analysis failed!") else: ops.integrator("DisplacementControl", 2, dof, incr_phi) while True: ok = ops.analyze(1) curr_M = ops.getLoadFactor(2) curr_Phi = ops.nodeDisp(2, dof) cond1 = np.abs(curr_M) < np.max(np.abs(M)) * (stop_ratio - 0.02) cond2 = np.abs(curr_Phi) > max_phi PHI.append(ops.nodeDisp(2, dof)) M.append(curr_M) FIBER_RESPONSES.append(_get_fiber_sec_data(ele_tag=1)) if cond1 or cond2: break if ok < 0: raise RuntimeError("Analysis failed!") FIBER_RESPONSES[0] = FIBER_RESPONSES[1] * 0 return np.abs(PHI), np.abs(M), np.array(FIBER_RESPONSES) def _get_fiber_sec_data(ele_tag: int): fiber_data = ops.eleResponse(ele_tag, "section", "fiberData2") # From column 1 to 6: "yCoord", "zCoord", "area", 'mat', "stress", "strain" fiber_data = np.array(fiber_data).reshape((-1, 6)) return fiber_data def _get_center(ys, zs, areas): yo = np.sum(ys * areas) / np.sum(areas) zo = np.sum(zs * areas) / np.sum(areas) return yo, zo