Source code for pycba.inf_lines

"""
PyCBA - Continuous Beam Analysis - Influence Lines Module
"""
from typing import Optional, Union
import numpy as np
import matplotlib.pyplot as plt
from .analysis import BeamAnalysis


[docs]class InfluenceLines: """ Creates influence lines for an arbitrary beam configuration using CBA """ def __init__( self, L: np.ndarray, EI: Union[float, np.ndarray], R: np.ndarray, eletype: Optional[np.ndarray] = None, ): """ Constructs an influence line object for a beam Parameters ---------- L : np.ndarray A vector of span lengths. EI : Union[float, np.ndarray] A vector of member flexural rigidities. R : np.ndarray A vector describing the support conditions at each member end. eletype : Optional[np.ndarray] A vector of the member types. Defaults to a fixed-fixed element. Returns ------- None. """ self.ba = BeamAnalysis(L=L, EI=EI, R=R, eletype=eletype) self.L = self.ba.beam.length self.vResults = [] self.pos = []
[docs] def create_ils(self, step: Optional[float] = None, load_val: float = 1.0): """ Creates the influence lines by marching the unit load (`load_val`) across the defined beam configuration in `step` distance increments, storing the static analysis results in a vector of :class:`pycba.results.BeamResults`. Parameters ---------- step : Optional[float] The distance increment to move the unit load; defaults to beam length / 100. load_val : float, optional The nominal value of the "unit load". The default is 1.0. Raises ------ ValueError If a static beam analysis does not succeed, usually due to a beam configuration error. Returns ------- None. """ self.vResults = [] # reset if step is None: step = self.L / 100 npts = round(self.L / step) + 1 for i in range(npts): # load position pos = i * step self.pos.append(pos) # locate load on span ispan, pos_in_span = self.ba.beam.get_local_span_coords(pos) if ispan == -1: load_val = 0.0 # assemble and set load matrix self.ba.set_loads([[ispan + 1, 2, load_val, pos_in_span, 0]]) # analyze out = self.ba.analyze() if out != 0: raise ValueError("IL analysis did not succeed") return self.vResults.append(self.ba.beam_results)
[docs] def get_il(self, poi: float, load_effect: str) -> (np.ndarray, np.ndarray): """ Returns the influence line at a point of interest for a load effect. Parameters ---------- poi : float The position of interest in global coordinates along the length of the beam. load_effect : str A single character to identify the load effect of interest, currently one of: - **V**: shear force - **M**: bending moment - **R**: vertical reaction at a fully restrained support The vertical reaction nearest the `poi` is used. For moment reactions use a poi at or just beside the support. Returns ------- (x,eta) : tuple(np.ndarray,np.ndarray) A tuple of the vectors of abcissa and influence ordinates. """ if not self.vResults: self.create_ils() x = self.vResults[0].results.x npts = len(self.vResults) eta = np.zeros(npts) # Preparations for reaction ILs # # Get vector of the node locations node_locations = np.cumsum(np.insert(self.ba.beam.mbr_lengths, 0, 0)) # Link the supported DOF to the index in the BeamAnalysis reactions vector idx_mask = np.zeros_like(self.ba._beam.restraints) idx_mask[np.where(np.array(self.ba._beam.restraints) == -1)] = np.arange( self.ba.beam.no_fixed_restraints ) # idx = np.abs(x - poi).argmin() if load_effect.upper() == "V": dx = x[2] - x[1] idx = np.where(np.abs(x - poi) <= dx * 1e-6)[0][0] for i, res in enumerate(self.vResults): eta[i] = res.results.V[idx] elif load_effect.upper() == "R": # # Getting the correct reaction is tricky # # The indices of the supported DOFs wrt the node locations vector vert_sups_dof_idx = np.where(np.array(self.ba._beam.restraints)[::2] == -1)[ 0 ] # The locations then of these supports vert_sups_locs = node_locations[vert_sups_dof_idx] # The index of the closest support closest_vert_sup_idx = np.abs(vert_sups_locs - poi).argmin() # And its value closest_vert_sup = vert_sups_locs[closest_vert_sup_idx] # And now the index of this support in the node locations vector vert_sup_node_idx = np.where(node_locations == closest_vert_sup)[0][0] # And hence its index in the overall DOFs vector vert_sup_dof_idx = 2 * vert_sup_node_idx # And finally the index of the support nearest the POI in the reactions vector vert_sup_idx = idx_mask[vert_sup_dof_idx] for i, res in enumerate(self.vResults): eta[i] = res.R[vert_sup_idx] elif load_effect.upper() == "MR": # # Follows the same logic for the vertical reaction # mt_sups_dof_idx = np.where(np.array(self.ba._beam.restraints)[1::2] == -1)[ 0 ] mt_sups_locs = node_locations[mt_sups_dof_idx] closest_mt_sup_idx = np.abs(mt_sups_locs - poi).argmin() closest_mt_sup = mt_sups_locs[closest_mt_sup_idx] mt_sup_node_idx = np.where(node_locations == closest_mt_sup)[0][0] mt_sup_dof_idx = 2 * mt_sup_node_idx + 1 mt_sup_idx = idx_mask[mt_sup_dof_idx] for i, res in enumerate(self.vResults): eta[i] = res.R[mt_sup_idx] else: dx = x[2] - x[1] idx = np.where(np.abs(x - poi) <= dx * 1e-6)[0][0] for i, res in enumerate(self.vResults): eta[i] = res.results.M[idx] return (np.array(self.pos), eta)
[docs] def plot_il(self, poi: float, load_effect: str, ax: Optional[plt.Axes] = None): """ Retrieves and plots the IL on either a supplied or new axes. Parameters ---------- poi : float The position of interest in global coordinates along the length of the beam. load_effect : str A single character to identify the load effect of interest, currently one of: - **V**: shear force - **M**: bending moment - **R**: vertical reaction at a fully restrained support The vertical reaction nearest the `poi` is used. For moment reactions use a poi at or just beside the support. ax : Optional[plt.Axes] A user-supplied matplotlib Axes object; when None (default), one is created for the plot. """ (x, y) = self.get_il(poi, load_effect) if ax is None: fig, ax = plt.subplots() ax.plot([0, self.L], [0, 0], "k", lw=2) ax.plot(x, y, "r") ax.grid() ax.set_ylabel("Influence Ordinate") ax.set_xlabel("Distance along beam (m)") ax.set_title(f"IL for {load_effect} at {poi}") plt.tight_layout()