Source code for compmech.panel.assembly.assembly

from __future__ import division, absolute_import

import platform
import gc
from multiprocessing import Pool

import numpy as np
from numpy import linspace, reshape
from scipy.sparse import csr_matrix
import matplotlib.cm as cm

from compmech.logger import msg, warn
from compmech.constants import DOUBLE
from compmech.panel._panel import check_c
import compmech.panel.modelDB as modelDB
import compmech.panel.connections as connections
from compmech.sparse import make_symmetric, finalize_symmetric_matrix


def default_field(panel, gridx, gridy):
    xs = linspace(0, panel.a, gridx)
    ys = linspace(0, panel.b, gridy)
    xs, ys = np.meshgrid(xs, ys, copy=False)
    xs = np.atleast_1d(np.array(xs, dtype=DOUBLE))
    ys = np.atleast_1d(np.array(ys, dtype=DOUBLE))
    shape = xs.shape
    xs = xs.ravel()
    ys = ys.ravel()
    return xs, ys, shape


[docs] class PanelAssembly(object): r"""Class for Panel Assemblies This class has some useful methods that will help plotting output for different panel groups within the assembly and so forth. For more details about the theory involved, see [castro2017AssemblyModels]_. Parameters ---------- panels : iterable A list, tuple etc of :class:`.Panel` objects. conn : dict A connectivity dictionary. Example ------- >>> panel_1 = Panel(*args) >>> panel_2 = Panel(*args) >>> panel_3 = Panel(*args) >>> panels = [panel_1, panel_2] >>> conn = [ dict(p1=panel_1, p2=panel_2, func='SSxcte', xcte1=0, xcte2=panel_2.a), dict(p1=panel_2, p2=panel_3, func='SSycte', ycte1=0, ycte2=panel_3.b) ] # A list of dictionary that indicates two connections: (panel_1-panel_2) and (panel_2-panel_3) >>> assembly_1 = PanelAssembly(panels, conn) Notes ----- Concerning the conn dictionary, the key 'func' stands for the connection function that builds the compatibility relations between the panels. The connections functions available are: - 'SSycte' : defines a skin-skin connection and calls the following functions ``fkCSSycte11``, ``fkCSSycte12``, ``fkCSSycte22`` - 'SSxcte' : defines a skin-skin connection and calls the following functions ``fkCSSxcte11``, ``fkCSSxcte12``, ``fkCSSxcte22`` - 'SB' : defines a skin-base connection and calls the following functions ``fkCBFycte11``, ``fkCBFycte12``, ``fkCBFycte22`` - 'BFycte': defines a base-flange connection and calls the following functions ``fkCBFycte11``, ``fkCBFycte12``, ``fkCBFycte22`` Explanations about the connetion functions are found in ``connections`` module. """ def __init__(self, panels, conn=None): self.conn = conn self.k0_conn = None self.panels = panels self.size = None self.out_num_cores = 4 row0 = 0 col0 = 0 for p in panels: p.row_start = row0 p.col_start = col0 row0 += 3*p.m*p.n col0 += 3*p.m*p.n p.row_end = row0 p.col_end = col0 def get_size(self): self.size = sum([3*p.m*p.n for p in self.panels]) return self.size
[docs] def plot(self, c, group, invert_y=False, vec='w', filename='', ax=None, figsize=(3.5, 2.), save=True, title='', identify=False, show_boundaries=False, boundary_line='--k', boundary_linewidth=1., colorbar=False, cbar_nticks=2, cbar_format=None, cbar_title='', cbar_fontsize=10, colormap='jet', aspect='equal', clean=True, dpi=400, texts=[], xs=None, ys=None, gridx=50, gridy=50, num_levels=400, vecmin=None, vecmax=None, calc_data_only=False): r"""Contour plot for a Ritz constants vector. Parameters ---------- c : np.ndarray The Ritz constants that will be used to compute the field contour. group : str A group to plot. Each panel in ``panels`` should contain an attribute ``group``, which is used to identify which entities should be plotted together. vec : str, optional Can be one of the components: - Displacement: ``'u'``, ``'v'``, ``'w'``, ``'phix'``, ``'phiy'`` - Strain: ``'exx'``, ``'eyy'``, ``'gxy'``, ``'kxx'``, ``'kyy'``, ``'kxy'``, ``'gyz'``, ``'gxz'`` - Stress: ``'Nxx'``, ``'Nyy'``, ``'Nxy'``, ``'Mxx'``, ``'Myy'``, ``'Mxy'``, ``'Qy'``, ``'Qx'`` invert_y : bool, optional Inverts the `y` axis of the plot. save : bool, optional Flag telling whether the contour should be saved to an image file. dpi : int, optional Resolution of the saved file in dots per inch. filename : str, optional The file name for the generated image file. If no value is given, the `name` parameter of the ``Panel`` object will be used. ax : AxesSubplot, optional When ``ax`` is given, the contour plot will be created inside it. figsize : tuple, optional The figure size given by ``(width, height)``. title : str, optional If any string is given a title is added to the contour plot. indentify : bool, optional If domains should be identified. If yes, the name of each panel is used. show_boundaries : bool, optional If boundaries between domains should be drawn. boundary_line : str, optional Matplotlib string to define line type and color. boundary_linewidth : float, optional Matplotlib float to define line width. colorbar : bool, optional If a colorbar should be added to the contour plot. cbar_nticks : int, optional Number of ticks added to the colorbar. cbar_format : [ None | format string | Formatter object ], optional See the ``matplotlib.pyplot.colorbar`` documentation. cbar_title : str, optional Colorbar title. If ``cbar_title == ''`` no title is added. cbar_fontsize : int, optional Fontsize of the colorbar labels. colormap : string, optional Name of a matplotlib available colormap. aspect : str, optional String that will be passed to the ``AxesSubplot.set_aspect()`` method. clean : bool, optional Clean axes ticks, grids, spines etc. xs : np.ndarray, optional The `x` positions where to calculate the displacement field. Default is ``None`` and the method ``_default_field`` is used. ys : np.ndarray, optional The ``y`` positions where to calculate the displacement field. Default is ``None`` and the method ``_default_field`` is used. gridx : int, optional Number of points along the `x` axis where to calculate the displacement field. gridy : int, optional Number of points along the `y` where to calculate the displacement field. num_levels : int, optional Number of contour levels (higher values make the contour smoother). vecmin : float, optional Minimum value for the contour scale (useful to compare with other results). If not specified it will be taken from the calculated field. vecmax : float, optional Maximum value for the contour scale. calc_data_only : bool, optional If only calculated data should be returned. Returns ------- ax : matplotlib.axes.Axes The Matplotlib object that can be used to modify the current plot if needed. data : dict Data calculated during the plotting procedure. """ msg('Plotting contour...') import matplotlib if platform.system().lower() == 'linux': matplotlib.use('Agg') import matplotlib.pyplot as plt msg('Computing field variables...', level=1) displs = ['u', 'v', 'w', 'phix', 'phiy'] strains = ['exx', 'eyy', 'gxy', 'kxx', 'kyy', 'kxy', 'gyz', 'gxz'] stresses = ['Nxx', 'Nyy', 'Nxy', 'Mxx', 'Myy', 'Mxy', 'Qy', 'Qx'] if vec in displs: res = self.uvw(c, group, gridx=gridx, gridy=gridy) elif vec in strains: res = self.strain(c, group, gridx=gridx, gridy=gridy) elif vec in stresses: res = self.stress(c, group, gridx=gridx, gridy=gridy) else: raise ValueError( '{0} is not a valid vec parameter value!'.format(vec)) field = np.array(res[vec]) msg('Finished!', level=1) if vecmin is None: vecmin = field.min() if vecmax is None: vecmax = field.max() data = dict(vecmin=vecmin, vecmax=vecmax) if calc_data_only: return None, data levels = linspace(vecmin, vecmax, num_levels) if ax is None: fig = plt.figure(figsize=figsize) ax = fig.add_subplot(111) else: if isinstance(ax, matplotlib.axes.Axes): ax = ax fig = ax.figure save = False else: raise ValueError('ax must be an Axes object') if invert_y == True: ax.invert_yaxis() ax.invert_xaxis() colormap_obj = getattr(cm, colormap, None) if colormap_obj is None: warn('Invalid colormap, using "jet"', level=1) colormap_obj = cm.jet count = -1 for i, panel in enumerate(self.panels): if panel.group != group: continue count += 1 xplot = res['y'][count] + panel.y0 yplot = res['x'][count] + panel.x0 field = res[vec][count] contour = ax.contourf(xplot, yplot, field, levels=levels, cmap=colormap_obj) if identify: ax.text(xplot.mean(), yplot.mean(), 'P {0:02d}'.format(i+1), transform=ax.transData, ha='center') if show_boundaries: x1, x2 = xplot.min(), xplot.max() y1, y2 = yplot.min(), yplot.max() ax.plot((x1, x2), (y1, y1), boundary_line, lw=boundary_linewidth) ax.plot((x1, x2), (y2, y2), boundary_line, lw=boundary_linewidth) ax.plot((x1, x1), (y1, y2), boundary_line, lw=boundary_linewidth) ax.plot((x2, x2), (y1, y2), boundary_line, lw=boundary_linewidth) if colorbar: from mpl_toolkits.axes_grid1 import make_axes_locatable fsize = cbar_fontsize divider = make_axes_locatable(ax) cax = divider.append_axes('right', size='5%', pad=0.05) cbarticks = linspace(vecmin, vecmax, cbar_nticks) cbar = plt.colorbar(contour, ticks=cbarticks, format=cbar_format, cax=cax) if cbar_title: cax.text(0.5, 1.05, cbar_title, horizontalalignment='center', verticalalignment='bottom', fontsize=fsize) cbar.outline.remove() cbar.ax.tick_params(labelsize=fsize, pad=0., tick2On=False) if title != '': ax.set_title(str(title)) fig.tight_layout() ax.set_aspect(aspect) ax.grid(False) ax.set_frame_on(False) if clean: ax.xaxis.set_ticks_position('none') ax.yaxis.set_ticks_position('none') ax.xaxis.set_ticklabels([]) ax.yaxis.set_ticklabels([]) else: ax.xaxis.set_ticks_position('bottom') ax.yaxis.set_ticks_position('left') for kwargs in texts: ax.text(transform=ax.transAxes, **kwargs) if save: if not filename: filename = group + '.png' fig.savefig(filename, transparent=True, bbox_inches='tight', pad_inches=0.05, dpi=dpi) plt.close() msg('finished!') return ax, data
[docs] def uvw(self, c, group, gridx=50, gridy=50): r"""Calculate the displacement field For a given full set of Ritz constants ``c``, the displacement field is calculated and stored in the parameters ``u``, ``v``, ``w``, ``phix``, ``phiy`` of the ``Panel`` object. Parameters ---------- c : float The full set of Ritz constants group : str A group to plot. Each panel in ``panels`` should contain an attribute ``group``, which is used to identify which entities should be plotted together. gridx : int, optional Number of points along the `x` axis where to calculate the displacement field. gridy : int, optional Number of points along the `y` where to calculate the displacement field. Returns ------- out : tuple A tuple of ``np.ndarrays`` containing ``(xs, ys, u, v, w, phix, phiy)``. Notes ----- The returned values ``u```, ``v``, ``w``, ``phix``, ``phiy`` are stored as parameters with the same name in the ``Panel`` object. """ res = dict(x=[], y=[], u=[], v=[], w=[], phix=[], phiy=[]) for panel in self.panels: if panel.group != group: continue c_panel = c[panel.col_start: panel.col_end] c_panel = np.ascontiguousarray(c_panel, dtype=DOUBLE) model = panel.model fuvw = modelDB.db[model]['field'].fuvw x, y, shape = default_field(panel, gridx, gridy) x = np.ascontiguousarray(x) y = np.ascontiguousarray(y) u, v, w, phix, phiy = fuvw(c_panel, panel, x, y, self.out_num_cores) res['x'].append(reshape(x, shape)) res['y'].append(reshape(y, shape)) res['u'].append(reshape(u, shape)) res['v'].append(reshape(v, shape)) res['w'].append(reshape(w, shape)) res['phix'].append(reshape(phix, shape)) res['phiy'].append(reshape(phiy, shape)) return res
[docs] def strain(self, c, group, gridx=50, gridy=50, NLterms=True): r"""Calculate the strain field Parameters ---------- c : float The full set of Ritz constants group : str A group to plot. Each panel in ``panels`` should contain an attribute ``group``, which is used to identify which entities should be plotted together. gridx : int, optional Number of points along the `x` axis where to calculate the displacement field. gridy : int, optional Number of points along the `y` where to calculate the displacement field. NLterms : bool Flag to indicate whether non-linear strain components should be considered. Returns ------- out : dict A dictionary of ``np.ndarrays`` with the keys: ``(x, y, exx, eyy, gxy, kxx, kyy, kxy)``. """ res = dict(x=[], y=[], exx=[], eyy=[], gxy=[], kxx=[], kyy=[], kxy=[]) for panel in self.panels: if panel.group != group: continue c_panel = c[panel.col_start: panel.col_end] c_panel = np.ascontiguousarray(c_panel, dtype=DOUBLE) model = panel.model fstrain = modelDB.db[model]['field'].fstrain x, y, shape = default_field(panel, gridx, gridy) x = np.ascontiguousarray(x) y = np.ascontiguousarray(y) exx, eyy, gxy, kxx, kyy, kxy = fstrain(c_panel, panel, x, y, self.out_num_cores, NLterms=int(NLterms)) res['x'].append(reshape(x, shape)) res['y'].append(reshape(y, shape)) res['exx'].append(reshape(exx, shape)) res['eyy'].append(reshape(eyy, shape)) res['gxy'].append(reshape(gxy, shape)) res['kxx'].append(reshape(kxx, shape)) res['kyy'].append(reshape(kyy, shape)) res['kxy'].append(reshape(kxy, shape)) return res
[docs] def stress(self, c, group, gridx=50, gridy=50, NLterms=True): r"""Calculate the stress field Parameters ---------- c : float The full set of Ritz constants group : str A group to plot. Each panel in ``panels`` should contain an attribute ``group``, which is used to identify which entities should be plotted together. gridx : int, optional Number of points along the `x` axis where to calculate the displacement field. gridy : int, optional Number of points along the `y` where to calculate the displacement field. NLterms : bool Flag to indicate whether non-linear strain components should be considered. Returns ------- out : dict A dict containing many ``np.ndarrays``, with the keys: ``(x, y, Nxx, Nyy, Nxy, Mxx, Myy, Mxy)``. """ res = dict(x=[], y=[], Nxx=[], Nyy=[], Nxy=[], Mxx=[], Myy=[], Mxy=[]) for panel in self.panels: if panel.group != group: continue c_panel = c[panel.col_start: panel.col_end] c_panel = np.ascontiguousarray(c_panel, dtype=DOUBLE) model = panel.model fstrain = modelDB.db[model]['field'].fstrain x, y, shape = default_field(panel, gridx, gridy) x = np.ascontiguousarray(x) y = np.ascontiguousarray(y) exx, eyy, gxy, kxx, kyy, kxy = fstrain(c_panel, panel, x, y, self.out_num_cores, NLterms=int(NLterms)) exx = reshape(exx, shape) eyy = reshape(eyy, shape) gxy = reshape(gxy, shape) kxx = reshape(kxx, shape) kyy = reshape(kyy, shape) kxy = reshape(kxy, shape) Ns = np.zeros((exx.shape + (6,))) F = panel.F if F is None: raise ValueError('Laminate ABD matrix not defined for panel') for i in range(6): Ns[..., i] = (exx*F[i, 0] + eyy*F[i, 1] + gxy*F[i, 2] + kxx*F[i, 3] + kyy*F[i, 4] + kxy*F[i, 5]) res['x'].append(reshape(x, shape)) res['y'].append(reshape(y, shape)) res['Nxx'].append(Ns[..., 0]) res['Nyy'].append(Ns[..., 1]) res['Nxy'].append(Ns[..., 2]) res['Mxx'].append(Ns[..., 3]) res['Myy'].append(Ns[..., 4]) res['Mxy'].append(Ns[..., 5]) return res
def get_k0_conn(self, conn=None, finalize=True): if conn is None: if self.conn is None: raise RuntimeError('No connectivity dictionary defined!') conn = self.conn if self.k0_conn is not None: return self.k0_conn size = self.get_size() k0_conn = 0. for connecti in conn: p1 = connecti['p1'] p2 = connecti['p2'] connection_function = connecti['func'] if connection_function == 'SSycte': kt, kr = connections.calc_kt_kr(p1, p2, 'ycte') k0_conn += connections.kCSSycte.fkCSSycte11( kt, kr, p1, connecti['ycte1'], size, p1.row_start, col0=p1.col_start) k0_conn += connections.kCSSycte.fkCSSycte12( kt, kr, p1, p2, connecti['ycte1'], connecti['ycte2'], size, p1.row_start, col0=p2.col_start) k0_conn += connections.kCSSycte.fkCSSycte22( kt, kr, p1, p2, connecti['ycte2'], size, p2.row_start, col0=p2.col_start) elif connection_function == 'SSxcte': kt, kr = connections.calc_kt_kr(p1, p2, 'xcte') k0_conn += connections.kCSSxcte.fkCSSxcte11( kt, kr, p1, connecti['xcte1'], size, p1.row_start, col0=p1.col_start) k0_conn += connections.kCSSxcte.fkCSSxcte12( kt, kr, p1, p2, connecti['xcte1'], connecti['xcte2'], size, p1.row_start, col0=p2.col_start) k0_conn += connections.kCSSxcte.fkCSSxcte22( kt, kr, p1, p2, connecti['xcte2'], size, p2.row_start, col0=p2.col_start) elif connection_function == 'SB': kt, kr = connections.calc_kt_kr(p1, p2, 'bot-top') dsb = sum(p1.plyts)/2. + sum(p2.plyts)/2. k0_conn += connections.kCSB.fkCSB11(kt, dsb, p1, size, p1.row_start, col0=p1.col_start) k0_conn += connections.kCSB.fkCSB12(kt, dsb, p1, p2, size, p1.row_start, col0=p2.col_start) k0_conn += connections.kCSB.fkCSB22(kt, p1, p2, size, p2.row_start, col0=p2.col_start) elif connection_function == 'BFycte': kt, kr = connections.calc_kt_kr(p1, p2, 'ycte') k0_conn += connections.kCBFycte.fkCBFycte11( kt, kr, p1, connecti['ycte1'], size, p1.row_start, col0=p1.col_start) k0_conn += connections.kCBFycte.fkCBFycte12( kt, kr, p1, p2, connecti['ycte1'], connecti['ycte2'], size, p1.row_start, col0=p2.col_start) k0_conn += connections.kCBFycte.fkCBFycte22( kt, kr, p1, p2, connecti['ycte2'], size, p2.row_start, col0=p2.col_start) elif connection_function == 'BFxcte': kt, kr = connections.calc_kt_kr(p1, p2, 'xcte') k0_conn += connections.kCBFxcte.fkCBFxcte11( kt, kr, p1, connecti['xcte1'], size, p1.row_start, col0=p1.col_start) k0_conn += connections.kCBFxcte.fkCBFxcte12( kt, kr, p1, p2, connecti['xcte1'], connecti['xcte2'], size, p1.row_start, col0=p2.col_start) k0_conn += connections.kCBFxcte.fkCBFxcte22( kt, kr, p1, p2, connecti['xcte2'], size, p2.row_start, col0=p2.col_start) elif connection_function == 'kCLTxycte': kt, kr = connections.calc_kt_kr(p1, p2, 'xcte-ycte') k0_conn += connections.kCLTxycte.fkCBFxcte11( kt, kr, p1, connecti['xcte1'], size, p1.row_start, col0=p1.col_start) k0_conn += connections.kCLTxycte.fkCBFxycte12( kt, kr, p1, p2, connecti['xcte1'], connecti['ycte2'], size, p1.row_start, col0=p2.col_start) k0_conn += connections.kCLTxycte.fkCBFycte22( kt, kr, p1, p2, connecti['ycte2'], size, p2.row_start, col0=p2.col_start) else: raise ValueError(f'{connection_function} not recognized.') if finalize: k0_conn = finalize_symmetric_matrix(k0_conn) self.k0_conn = k0_conn #NOTE memory cleanup gc.collect() return k0_conn
[docs] def calc_k0(self, conn=None, c=None, silent=False, finalize=True, inc=1.): """Calculate the constitutive stiffness matrix of the assembly Parameters ---------- conn : dict, optional A connectivity dictionary. Optional if already defined for the assembly. c : array-like or None, optional This must be the result of a static analysis, used to compute non-linear terms based on the actual displacement field. silent : bool, optional A boolean to tell whether the log messages should be printed. finalize : bool, optional Asserts validity of output data and makes the output matrix symmetric, should be ``False`` when assemblying. inc : float, optional Dummy argument needed for non-linear analyses. """ size = self.get_size() if c is None: msg('Calculating k0 for assembly...', level=2, silent=silent) else: check_c(c, size) msg('Calculating kL for assembly...', level=2, silent=silent) k0 = 0. for p in self.panels: if p.row_start is None or p.col_start is None: raise ValueError('Panel attributes "row_start" and "col_start" must be defined!') k0 += p.calc_k0(c=c, row0=p.row_start, col0=p.col_start, size=size, silent=True, finalize=False) if finalize: k0 = finalize_symmetric_matrix(k0) k0_conn = self.get_k0_conn(conn=conn) k0 += self.k0_conn self.k0 = k0 msg('finished!', level=2, silent=silent) return k0
[docs] def calc_kG0(self, c=None, silent=False, finalize=True): """Calculate the geometric stiffness matrix of the assembly Parameters ---------- c : array-like or None, optional This must be the result of a static analysis, used to compute non-linear terms based on the actual displacement field. silent : bool, optional A boolean to tell whether the log messages should be printed. finalize : bool, optional Asserts validity of output data and makes the output matrix symmetric, should be ``False`` when assemblying. """ size = self.get_size() if c is None: msg('Calculating kG0 for assembly...', level=2, silent=silent) else: check_c(c, size) msg('Calculating kG for assembly...', level=2, silent=silent) kG0 = 0. for p in self.panels: if p.row_start is None or p.col_start is None: raise ValueError('Panel attributes "row_start" and "col_start" must be defined!') kG0 += p.calc_kG0(c=c, row0=p.row_start, col0=p.col_start, size=size, silent=True, finalize=False) if finalize: kG0 = finalize_symmetric_matrix(kG0) self.kG0 = kG0 msg('finished!', level=2, silent=silent) return kG0
def calc_kM(self, silent=False, finalize=True): msg('Calculating kM for assembly...', level=2, silent=silent) size = self.get_size() kM = 0 for p in self.panels: if p.row_start is None or p.col_start is None: raise ValueError('Panel attributes "row_start" and "col_start" must be defined!') kM += p.calc_kM(row0=p.row_start, col0=p.col_start, size=size, silent=True, finalize=False) if finalize: kM = finalize_symmetric_matrix(kM) self.kM = kM msg('finished!', level=2, silent=silent) return kM def calc_kT(self, c=None, silent=False, finalize=True, inc=None): msg('Calculating kT for assembly...', level=2, silent=silent) size = self.get_size() kT = 0 #TODO use multiprocessing.Pool here for p in self.panels: if p.row_start is None or p.col_start is None: raise ValueError('Panel attributes "row_start" and "col_start" must be defined!') kT += p.calc_k0(c=c, size=size, row0=p.row_start, col0=p.col_start, silent=True, finalize=False, inc=inc, NLgeom=True) kT += p.calc_kG0(c=c, size=size, row0=p.row_start, col0=p.col_start, silent=True, finalize=False, NLgeom=True) if finalize: kT = finalize_symmetric_matrix(kT) k0_conn = self.get_k0_conn() kT += k0_conn self.kT = kT msg('finished!', level=2, silent=silent) return kT def calc_fint(self, c, silent=False, inc=1.): msg('Calculating internal forces for assembly...', level=2, silent=silent) size = self.get_size() fint = 0 for p in self.panels: if p.col_start is None: raise ValueError('Panel attributes "col_start" must be defined!') fint += p.calc_fint(c=c, size=size, col0=p.col_start, silent=True) k0_conn = self.get_k0_conn() fint += k0_conn*c self.fint = fint msg('finished!', level=2, silent=silent) return fint def calc_fext(self, inc=1., silent=False): msg('Calculating external forces for assembly...', level=2, silent=silent) size = self.get_size() fext = 0 for p in self.panels: if p.col_start is None: raise ValueError('Panel attributes "col_start" must be defined!') fext += p.calc_fext(inc=inc, size=size, col0=p.col_start, silent=True) self.fext = fext msg('finished!', level=2, silent=silent) return fext