def _rebuild(self): assert self.panel1.model == self.panel2.model assert self.panel1.r == self.panel2.r assert self.panel1.alphadeg == self.panel2.alphadeg a = None b = None if self.panel1 is not None: a = self.panel1.a b = self.panel1.b elif self.panel2 is not None: a = self.panel2.a b = self.panel2.b if a is not None and b is not None: if a / b > 10.: if self.base.m <= 15 and self.flange.m <= 15: raise RuntimeError( 'For a/b > 10. use base.m and flange.m > 15') else: warn( 'For a/b > 10. be sure to check convergence for base.m and flange.m' ) self.flange.lam = laminate.read_stack( self.flange.stack, plyts=self.flange.plyts, laminaprops=self.flange.laminaprops) #NOTE below offset is not needed since it is already considered in the # connectivity matrices, using dpb h = 0.5 * sum(self.panel1.plyts) + 0.5 * sum(self.panel2.plyts) hb = sum(self.base.plyts) self.dpb = h / 2. + hb / 2. self.base.lam = laminate.read_stack(self.base.stack, plyts=self.base.plyts, laminaprops=self.base.laminaprops, offset=0.)
def _rebuild(self): if not self.name: try: self.name = os.path.basename(__main__.__file__).split('.py')[0] except AttributeError: warn('StiffPanelBay name unchanged') if self.a is None: raise ValueError('The length a must be specified') if self.b is None: raise ValueError('The width b must be specified') for p in self.panels: p._rebuild() if self.model is not None: assert self.model == p.model else: self.model = p.model for s in self.bladestiff1ds: s._rebuild() for s in self.bladestiff2ds: s._rebuild()
def _rebuild(self): assert self.panel1.model == self.panel2.model assert self.panel1.r == self.panel2.r assert self.panel1.alphadeg == self.panel2.alphadeg a = None b = None if self.panel1 is not None: a = self.panel1.a b = self.panel1.b elif self.panel2 is not None: a = self.panel2.a b = self.panel2.b if a is not None and b is not None: if a / b > 10.: if self.base.m <= 15 and self.flange.m <= 15: raise RuntimeError('For a/b > 10. use base.m and flange.m > 15') else: warn('For a/b > 10. be sure to check convergence for base.m and flange.m') self.flange.lam = laminate.read_stack(self.flange.stack, plyts=self.flange.plyts, laminaprops=self.flange.laminaprops) #NOTE below offset is not needed since it is already considered in the # connectivity matrices, using dpb h = 0.5*sum(self.panel1.plyts) + 0.5*sum(self.panel2.plyts) hb = sum(self.base.plyts) self.dpb = h/2. + hb/2. self.base.lam = laminate.read_stack(self.base.stack, plyts=self.base.plyts, laminaprops=self.base.laminaprops, offset=0.)
def static(self, NLgeom=False, silent=False): """Static analysis for cones and cylinders The analysis can be linear or geometrically non-linear. See :class:`.Analysis` for further details about the parameters controlling the non-linear analysis. Parameters ---------- NLgeom : bool Flag to indicate whether a linear or a non-linear analysis is to be performed. silent : bool, optional A boolean to tell whether the log messages should be printed. Returns ------- cs : list A list containing the Ritz constants for each load increment of the static analysis. The list will have only one entry in case of a linear analysis. Notes ----- The returned ``cs`` is stored in ``self.analysis.cs``. The actual increments used in the non-linear analysis are stored in the ``self.analysis.increments`` parameter. """ if self.c0 is not None: self.analysis.kT_initial_state = True else: self.analysis.kT_initial_state = False if NLgeom and not modelDB.db[self.model]['non-linear static']: msg('________________________________________________', silent=silent) msg('', silent=silent) warn('Model {} cannot be used in non-linear static analysis!'. format(self.model), silent=silent) msg('________________________________________________', silent=silent) raise elif not NLgeom and not modelDB.db[self.model]['linear static']: msg('________________________________________________', level=1, silent=silent) msg('', level=1, silent=silent) warn('Model {} cannot be used in linear static analysis!'. format(self.model), level=1, silent=silent) msg('________________________________________________', level=1, silent=silent) raise self.analysis.static(NLgeom=NLgeom, silent=silent) self.increments = self.analysis.increments return self.analysis.cs
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
def _solver_NR(run, silent=False): """Newton-Raphson solver """ msg('Initialization...', level=1, silent=silent) modified_NR = run.modified_NR inc = run.initialInc total = inc once_at_total = False max_total = 0. fext = run.calc_fext(inc=inc, silent=silent) k0 = run.calc_k0(silent=silent) c = solve(k0, fext, silent=silent) kT_last = k0 if modified_NR: compute_kT = False else: compute_kT = True step_num = 1 while True: msg('Started Load Step {} - '.format(step_num) + 'Attempting time = {0}'.format(total), level=1, silent=silent) # TODO maybe for pdC=True, pdT the fext must be calculated with # the last kT available... absERR = 1.e6 relERR = 1.e6 min_Rmax = 1.e6 prev_Rmax = 1.e6 last_min_Rmax = 1.e6 iteration = 0 converged = False kT = kT_last fext = run.calc_fext(inc=total, silent=silent) iter_NR = 0 while True: iteration += 1 msg('Iteration: {}'.format(iteration), level=2, silent=silent) if iteration > run.maxNumIter: warn('Maximum number of iterations achieved!', level=2, silent=silent) break if compute_kT or (run.kT_initial_state and step_num == 1 and iteration == 1) or iter_NR == (run.compute_every_n - 1): iter_NR = 0 kT = run.calc_kT(c=c, inc=total, silent=silent) else: iter_NR += 1 if not modified_NR: compute_kT = True fint = run.calc_fint(c=c, inc=total, silent=silent) R = fext - fint # convergence criteria: # - maximum residual force Rmax Rmax = np.abs(R).max() msg('Rmax = {0}'.format(Rmax), level=3, silent=silent) if iteration >= 2 and Rmax < run.absTOL: converged = True break if (Rmax > prev_Rmax and Rmax > min_Rmax and iteration > 2): warn('Diverged!', level=2, silent=silent) break else: min_Rmax = min(min_Rmax, Rmax) change_rate_Rmax = abs(prev_Rmax - Rmax) / abs(prev_Rmax) if (iteration > 2 and change_rate_Rmax < run.too_slow_TOL): warn('Diverged! (convergence too slow)', level=2, silent=silent) break prev_Rmax = Rmax msg('Solving... ', level=2, silent=silent) delta_c = solve(kT, R, silent=silent) msg('finished!', level=2, silent=silent) eta1 = 0. eta2 = 1. if run.line_search: msg('Performing line-search... ', level=2, silent=silent) iter_line_search = 0 while True: c1 = c + eta1 * delta_c c2 = c + eta2 * delta_c fint1 = run.calc_fint(c=c1, inc=total, silent=silent) fint2 = run.calc_fint(c=c2, inc=total, silent=silent) R1 = fext - fint1 R2 = fext - fint2 s1 = delta_c.dot(R1) s2 = delta_c.dot(R2) eta_new = (eta2 - eta1) * (-s1 / (s2 - s1)) + eta1 eta1 = eta2 eta2 = eta_new eta2 = min(max(eta2, 0.2), 10.) if abs(eta2 - eta1) < 0.01: break iter_line_search += 1 if iter_line_search == run.max_iter_line_search: eta2 = 1. warn('maxinum number of iterations', level=3, silent=silent) break msg('finished!', level=2, silent=silent) c = c + eta2 * delta_c if converged: msg('Finished Load Step {} at'.format(step_num) + ' time = {0}'.format(total), level=1, silent=silent) run.increments.append(total) run.cs.append(c.copy()) #NOTE copy required finished = False if abs(total - 1) < 1e-3: finished = True else: factor = 1.1 if once_at_total: inc_new = min(factor * inc, run.maxInc, (1. - total) / 2) else: inc_new = min(factor * inc, run.maxInc, 1. - total) msg('Changing time increment from {0:1.9f} to {1:1.9f}'.format( inc, inc_new), level=1, silent=silent) inc = inc_new total += inc total = min(1, total) step_num += 1 if finished: break if modified_NR: msg('Updating kT...', level=1, silent=silent) kT = run.calc_kT(c=c, inc=total, silent=silent) msg('kT updated!', level=1, silent=silent) compute_kT = False kT_last = kT else: max_total = max(max_total, total) while True: factor = 0.3 msg('Bisecting time increment from {0} to {1}'.format( inc, inc * factor), level=1, silent=silent) if abs(total - 1) < 1e-3: once_at_total = True total -= inc inc *= factor if inc < run.minInc: msg('Minimum step size achieved!', level=1, silent=silent) break total += inc if total >= max_total: continue else: break if inc < run.minInc: msg('Stopping solver: minimum step size achieved!', level=1, silent=silent) break if len(run.cs) > 0: c = run.cs[-1].copy() #NOTE copy required else: # means that run bisection must be done in initialInc fext = run.calc_fext(inc=inc, silent=silent) c = solve(k0, fext, silent=silent) msg('Finished Non-Linear Static Analysis', silent=silent) msg('at time {0}'.format(total), level=1, silent=silent)
def freq(self, atype=4, tol=0, sparse_solver=False, silent=False, sort=True, reduced_dof=False): """Performs a frequency analysis The following parameters of the ``AeroPistonPlate`` object will affect the linear buckling analysis: ======================= ===================================== parameter description ======================= ===================================== ``num_eigenvalues`` Number of eigenvalues to be extracted ``num_eigvalues_print`` Number of eigenvalues to print after the analysis is completed ======================= ===================================== Parameters ---------- atype : int, optional Tells which analysis type should be performed: - ``1`` : considers k0, kA and kG0 - ``2`` : considers k0 and kA - ``3`` : considers k0 and kG0 - ``4`` : considers k0 only tol : float, optional A tolerance value passed to ``scipy.sparse.linalg.eigs``. sparse_solver : bool, optional Tells if solver :func:`scipy.linalg.eig` or :func:`scipy.sparse.linalg.eigs` should be used. .. note:: It is recommended ``sparse_solver=False``, because it was verified that the sparse solver becomes unstable for some cases, though the sparse solver is faster. silent : bool, optional A boolean to tell whether the log messages should be printed. sort : bool, optional Sort the output eigenvalues and eigenmodes. reduced_dof : bool, optional Considers only the contributions of `w` to the stiffness matrix and considerably accelerates the run. Only effective when ``sparse_solver=False``. Notes ----- The extracted eigenvalues are stored in the ``eigvals`` parameter of the ``AeroPistonPlate`` object and the `i^{th}` eigenvector in the ``eigvecs[:, i-1]`` parameter. """ if not modelDB.db[self.model]['linear buckling']: msg('________________________________________________') msg('') warn('Model {} cannot be used in linear buckling analysis!'. format(self.model)) msg('________________________________________________') msg('Running frequency analysis...', silent=silent) if atype == 1: self.calc_linear_matrices(silent=silent) elif atype == 2: self.calc_linear_matrices(silent=silent, calc_kG0=False) elif atype == 3: self.calc_linear_matrices(silent=silent, calc_kA=False) elif atype == 4: self.calc_linear_matrices(silent=silent, calc_kA=False, calc_kG0=False) msg('Eigenvalue solver... ', level=2, silent=silent) if atype == 1: K = self.k0 - self.kA + self.kG0 elif atype == 2: K = self.k0 - self.kA elif atype == 3: K = self.k0 + self.kG0 elif atype == 4: K = self.k0 M = self.kM msg('eigs() solver...', level=3, silent=silent) k = min(self.num_eigvalues, M.shape[0]-2) if sparse_solver: eigvals, eigvecs = eigs(A=M, M=K, k=k, tol=tol, which='SM', sigma=-1.) else: M = M.toarray() K = K.toarray() if reduced_dof: i = np.arange(M.shape[0]) take = i[2::3] M = M[:, take][take, :] K = K[:, take][take, :] eigvals, eigvecs = eig(a=M, b=K) msg('finished!', level=3, silent=silent) eigvals = np.sqrt(1./eigvals) # omega^2 to omega, in rad/s if sort: sort_ind = np.lexsort((np.round(eigvals.imag, 1), np.round(eigvals.real, 1))) eigvals = eigvals[sort_ind] eigvecs = eigvecs[:, sort_ind] if not sparse_solver and reduced_dof: new_eigvecs = np.zeros((3*eigvecs.shape[0], eigvecs.shape[1])) new_eigvecs[2::3, :] = eigvecs eigvecs = new_eigvecs self.eigvals = eigvals self.eigvecs = eigvecs msg('finished!', level=2, silent=silent) msg('first {} eigenvalues:'.format(self.num_eigvalues_print), level=1, silent=silent) for eigval in eigvals[:self.num_eigvalues_print]: msg('{0} rad/s'.format(eigval), level=2, silent=silent) self.analysis.last_analysis = 'freq'
def lb(self, tol=0, combined_load_case=None, sparse_solver=True, calc_kA=False): """Performs a linear buckling analysis The following parameters of the ``AeroPistonPlate`` object will affect the linear buckling analysis: ======================= ===================================== parameter description ======================= ===================================== ``num_eigenvalues`` Number of eigenvalues to be extracted ``num_eigvalues_print`` Number of eigenvalues to print after the analysis is completed ======================= ===================================== Parameters ---------- combined_load_case : int, optional It tells whether the linear buckling analysis must be computed considering combined load cases, each value will tell the algorithm to rearrange the linear matrices in a different way. The valid values are ``1``, or ``2``, where: - ``1`` : find the critical Fx for a fixed Fxy - ``2`` : find the critical Fx for a fixed Fy - ``3`` : find the critical Fy for a fixed Fyx - ``4`` : find the critical Fy for a fixed Fx sparse_solver : bool, optional Tells if solver :func:`scipy.linalg.eigh` or :func:`scipy.sparse.linalg.eigs` should be used. Notes ----- The extracted eigenvalues are stored in the ``eigvals`` parameter of the ``AeroPistonPlate`` object and the `i^{th}` eigenvector in the ``eigvecs[:, i-1]`` parameter. """ if not modelDB.db[self.model]['linear buckling']: msg('________________________________________________') msg('') warn('Model {} cannot be used in linear buckling analysis!'. format(self.model)) msg('________________________________________________') msg('Running linear buckling analysis...') self.calc_linear_matrices(combined_load_case=combined_load_case, calc_kM=False, calc_kA=calc_kA) msg('Eigenvalue solver... ', level=2) if calc_kA: kA = self.kA else: kA = self.k0*0 if combined_load_case is None: M = self.k0 + kA A = self.kG0 elif combined_load_case == 1: M = self.k0 - kA + self.kG0_Fxy A = self.kG0_Fx elif combined_load_case == 2: M = self.k0 - kA + self.kG0_Fy A = self.kG0_Fx elif combined_load_case == 3: M = self.k0 - kA + self.kG0_Fyx A = self.kG0_Fy elif combined_load_case == 4: M = self.k0 - kA + self.kG0_Fx A = self.kG0_Fy Amin = abs(A.min()) # normalizing A to improve numerical stability A /= Amin if sparse_solver: try: msg('eigs() solver...', level=3) eigvals, eigvecs = eigs(A=A, k=self.num_eigvalues, which='SM', M=M, tol=tol, sigma=1.) msg('finished!', level=3) except Exception, e: warn(str(e), level=4) msg('aborted!', level=3) sizebkp = A.shape[0] M, A, used_cols = remove_null_cols(M, A) msg('eigs() solver...', level=3) eigvals, peigvecs = eigs(A=A, k=self.num_eigvalues, which='SM', M=M, tol=tol, sigma=1.) msg('finished!', level=3) eigvecs = np.zeros((sizebkp, self.num_eigvalues), dtype=DOUBLE) eigvecs[used_cols, :] = peigvecs # Un-normalizing eigvals eigvals *= Amin
def _rebuild(self): if not self.name: try: self.name = os.path.basename(__main__.__file__).split('.py')[0] except AttributeError: warn('AeroPistonPlate name unchanged') self.model = self.model.lower() valid_models = sorted(modelDB.db.keys()) if not self.model in valid_models: raise ValueError('ERROR - valid models are:\n ' + '\n '.join(valid_models)) # boundary conditions inf = self.inf zero = self.zero if inf > self.maxinf: warn('inf reduced to {0:1.1e4} due to the verified'.format( self.maxinf) + ' numerical instability for higher values', level=2) inf = self.maxinf if self.bc is not None: bc = self.bc.lower() if '_' in bc: # different bc for Bot, Top, Left and Right bc_Bot, bc_Top, bc_Left, bc_Right = self.bc.split('_') elif '-' in bc: # different bc for Bot, Top, Left and Right bc_Bot, bc_Top, bc_Left, bc_Right = self.bc.split('-') else: bc_Bot = bc_Top = bc_Left = bc_Right = bc bcs = dict(bc_Bot=bc_Bot, bc_Top=bc_Top, bc_Left=bc_Left, bc_Right=bc_Right) for k in bcs.keys(): sufix = k.split('_')[1] # Bot or Top if bcs[k] == 'ss1': setattr(self, 'ku' + sufix, inf) setattr(self, 'kv' + sufix, inf) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) elif bcs[k] == 'ss2': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, inf) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) elif bcs[k] == 'ss3': setattr(self, 'ku' + sufix, inf) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) elif bcs[k] == 'ss4': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) elif bcs[k] == 'cc1': setattr(self, 'ku' + sufix, inf) setattr(self, 'kv' + sufix, inf) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, inf) setattr(self, 'kphiy' + sufix, inf) elif bcs[k] == 'cc2': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, inf) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, inf) setattr(self, 'kphiy' + sufix, inf) elif bcs[k] == 'cc3': setattr(self, 'ku' + sufix, inf) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, inf) setattr(self, 'kphiy' + sufix, inf) elif bcs[k] == 'cc4': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, inf) setattr(self, 'kphiy' + sufix, inf) elif bcs[k] == 'free': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, zero) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) else: txt = '"{}" is not a valid boundary condition!'.format(bc) raise ValueError(txt) if self.a is None: raise ValueError('The length a must be specified') if self.b is None: raise ValueError('The width b must be specified') if not self.laminaprops: self.laminaprops = [self.laminaprop for i in self.stack] if not self.plyts: self.plyts = [self.plyt for i in self.stack] # defining load components from force vectors if self.laminaprop is None: raise ValueError('laminaprop must be defined')
def inv_weighted(data, mesh, num_sub, col, ncp=5, power_parameter=2): r"""Interpolates the values taken at one group of points into another using an inverse-weighted algorithm In the inverse-weighted algorithm a number of `n_{CP}` measured points of the input parameter ``data`` that are closest to a given node in the input parameter ``mesh`` are found and the imperfection value of this node (represented by the normal displacement `{w_0}_{node}`) is calculated as follows: .. math:: {w_0}_{node} = \frac{\sum_{i}^{n_{CP}}{{w_0}_i\frac{1}{w_i}}} {\sum_{i}^{n_{CP}}{\frac{1}{w_i}}} where `w_i` is the imperfection at each measured point, calculated as: .. math:: w_i = \left[(x_{node}-x_i)^2+(y_{node}-y_i)^2+(z_{node}-y_i)^2 \right]^p with `p` being a power parameter that when increased will increase the relative influence of a closest point. Parameters ---------- data : numpy.ndarray, shape (N, ndim+1) The data or an array containing the imperfection file. The values to be interpolated must be in the last column. mesh : numpy.ndarray, shape (M, ndim) The new coordinates where the values will be interpolated to. num_sub : int The number of sub-sets used during the interpolation. The points are divided in sub-sets to increase the algorithm's efficiency. col : int The index of the column to be used in order to divide the data in sub-sets. Note that the first column index is ``0``. ncp : int, optional Number of closest points used in the inverse-weighted interpolation. power_parameter : float, optional Power of inverse weighted interpolation function. Returns ------- ans : numpy.ndarray A 1-D array with the interpolated values. The size of this array is ``mesh.shape[0]``. """ if mesh.shape[1] != data.shape[1]-1: raise ValueError('Invalid input: mesh.shape[1] != data.shape[1]') msg('Interpolating... ') num_sub = int(num_sub) mesh_size = mesh.shape[0] # memory control mem_limit = 1024*1024*1024*8*2 # 2 GB mem_entries = int(mem_limit / 64) # if float64 is used sec_size = int(mesh_size/num_sub) while sec_size**2*10 > mem_entries: num_sub +=1 sec_size = int(mesh_size/num_sub) if sec_size**2*10 <= mem_entries: warn('New num_sub: {0}'.format(int(mesh_size/float(sec_size)))) break mesh_seq = np.arange(mesh.shape[0]) mesh_argsort = np.argsort(mesh[:, col]) mesh_seq = mesh_seq[mesh_argsort] back_argsort = np.argsort(mesh_seq) mesh = np.asarray(mesh[mesh_argsort], order='F') length = mesh[:, col].max() - mesh[:, col].min() data = np.asarray(data[np.argsort(data[:, col])], order='F') ans = np.zeros(mesh.shape[0], dtype=mesh.dtype) # max_num_limits defines how many times the log will print # "processed ... out of ... entries" max_num_limits = 10 for den in range(max_num_limits, 0, -1): if num_sub % den == 0: limit = int(num_sub/den) break for i in range(num_sub+1): i_inf = sec_size*i i_sup = sec_size*(i+1) if i % limit == 0: msg('\t processed {0:7d} out of {1:7d} entries'.format( min(i_sup, mesh_size), mesh_size)) sub_mesh = mesh[i_inf : i_sup] if not np.any(sub_mesh): continue inf = sub_mesh[:, col].min() sup = sub_mesh[:, col].max() tol = 0.03 if i == 0 or i == num_sub: tol = 0.06 while True: cond1 = data[:, col] >= inf - tol*length cond2 = data[:, col] <= sup + tol*length cond = np.all(np.array((cond1, cond2)), axis=0) sub_data = data[cond] if not np.any(sub_data): tol += 0.01 else: break dist = np.subtract.outer(sub_mesh[:, 0], sub_data[:, 0])**2 for j in range(1, sub_mesh.shape[1]): dist += np.subtract.outer(sub_mesh[:, j], sub_data[:, j])**2 asort = np.argsort(dist, axis=1) lenn = sub_mesh.shape[0] lenp = sub_data.shape[0] asort_mesh = asort + np.meshgrid(np.arange(lenn)*lenp, np.arange(lenp))[0].transpose() # getting the distance of the closest points dist_cp = np.take(dist, asort_mesh[:, :ncp]) # avoiding division by zero dist_cp[(dist_cp==0)] == 1.e-12 # fetching the imperfection of the sub-data imp = sub_data[:, -1] # taking only the imperfection of the closest points imp_cp = np.take(imp, asort[:, :ncp]) # weight calculation total_weight = np.sum(1./(dist_cp**power_parameter), axis=1) weight = 1./(dist_cp**power_parameter) # computing the new imp imp_new = np.sum(imp_cp*weight, axis=1)/total_weight # updating the answer array ans[i_inf : i_sup] = imp_new ans = ans[back_argsort] msg('Interpolation completed!') return ans
def inv_weighted(data, mesh, num_sub, col, ncp=5, power_parameter=2): r"""Interpolates the values taken at one group of points into another using an inverse-weighted algorithm In the inverse-weighted algorithm a number of `n_{CP}` measured points of the input parameter ``data`` that are closest to a given node in the input parameter ``mesh`` are found and the imperfection value of this node (represented by the normal displacement `{w_0}_{node}`) is calculated as follows: .. math:: {w_0}_{node} = \frac{\sum_{i}^{n_{CP}}{{w_0}_i\frac{1}{w_i}}} {\sum_{i}^{n_{CP}}{\frac{1}{w_i}}} where `w_i` is the imperfection at each measured point, calculated as: .. math:: w_i = \left[(x_{node}-x_i)^2+(y_{node}-y_i)^2+(z_{node}-y_i)^2 \right]^p with `p` being a power parameter that when increased will increase the relative influence of a closest point. Parameters ---------- data : numpy.ndarray, shape (N, ndim+1) The data or an array containing the imperfection file. The values to be interpolated must be in the last column. mesh : numpy.ndarray, shape (M, ndim) The new coordinates where the values will be interpolated to. num_sub : int The number of sub-sets used during the interpolation. The points are divided in sub-sets to increase the algorithm's efficiency. col : int The index of the column to be used in order to divide the data in sub-sets. Note that the first column index is ``0``. ncp : int, optional Number of closest points used in the inverse-weighted interpolation. power_parameter : float, optional Power of inverse weighted interpolation function. Returns ------- ans : numpy.ndarray A 1-D array with the interpolated values. The size of this array is ``mesh.shape[0]``. """ if mesh.shape[1] != data.shape[1] - 1: raise ValueError('Invalid input: mesh.shape[1] != data.shape[1]') msg('Interpolating... ') num_sub = int(num_sub) mesh_size = mesh.shape[0] # memory control mem_limit = 1024 * 1024 * 1024 * 8 * 2 # 2 GB mem_entries = int(mem_limit / 64) # if float64 is used sec_size = int(mesh_size / num_sub) while sec_size**2 * 10 > mem_entries: num_sub += 1 sec_size = int(mesh_size / num_sub) if sec_size**2 * 10 <= mem_entries: warn('New num_sub: {0}'.format(int(mesh_size / float(sec_size)))) break mesh_seq = np.arange(mesh.shape[0]) mesh_argsort = np.argsort(mesh[:, col]) mesh_seq = mesh_seq[mesh_argsort] back_argsort = np.argsort(mesh_seq) mesh = np.asarray(mesh[mesh_argsort], order='F') length = mesh[:, col].max() - mesh[:, col].min() data = np.asarray(data[np.argsort(data[:, col])], order='F') ans = np.zeros(mesh.shape[0], dtype=mesh.dtype) # max_num_limits defines how many times the log will print # "processed ... out of ... entries" max_num_limits = 10 for den in range(max_num_limits, 0, -1): if num_sub % den == 0: limit = int(num_sub / den) break for i in range(num_sub + 1): i_inf = sec_size * i i_sup = sec_size * (i + 1) if i % limit == 0: msg('\t processed {0:7d} out of {1:7d} entries'.format( min(i_sup, mesh_size), mesh_size)) sub_mesh = mesh[i_inf:i_sup] if not np.any(sub_mesh): continue inf = sub_mesh[:, col].min() sup = sub_mesh[:, col].max() tol = 0.03 if i == 0 or i == num_sub: tol = 0.06 while True: cond1 = data[:, col] >= inf - tol * length cond2 = data[:, col] <= sup + tol * length cond = np.all(np.array((cond1, cond2)), axis=0) sub_data = data[cond] if not np.any(sub_data): tol += 0.01 else: break dist = np.subtract.outer(sub_mesh[:, 0], sub_data[:, 0])**2 for j in range(1, sub_mesh.shape[1]): dist += np.subtract.outer(sub_mesh[:, j], sub_data[:, j])**2 asort = np.argsort(dist, axis=1) lenn = sub_mesh.shape[0] lenp = sub_data.shape[0] asort_mesh = asort + np.meshgrid( np.arange(lenn) * lenp, np.arange(lenp))[0].transpose() # getting the distance of the closest points dist_cp = np.take(dist, asort_mesh[:, :ncp]) # avoiding division by zero dist_cp[(dist_cp == 0)] == 1.e-12 # fetching the imperfection of the sub-data imp = sub_data[:, -1] # taking only the imperfection of the closest points imp_cp = np.take(imp, asort[:, :ncp]) # weight calculation total_weight = np.sum(1. / (dist_cp**power_parameter), axis=1) weight = 1. / (dist_cp**power_parameter) # computing the new imp imp_new = np.sum(imp_cp * weight, axis=1) / total_weight # updating the answer array ans[i_inf:i_sup] = imp_new ans = ans[back_argsort] msg('Interpolation completed!') return ans
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
def _solver_NR(a): """Newton-Raphson solver """ msg('Initialization...', level=1) modified_NR = a.modified_NR inc = a.initialInc total = inc once_at_total = False max_total = 0. fext = a.calc_fext(inc=inc) k0 = a.calc_k0() c = solve(k0, fext) kT_last = k0 if modified_NR: compute_kT = False else: compute_kT = True step_num = 1 while True: msg('Started Load Step {} - '.format(step_num) + 'Attempting time = {0}'.format(total), level=1) # TODO maybe for pdC=True, pdT the fext must be calculated with # the last kT available... absERR = 1.e6 relERR = 1.e6 min_Rmax = 1.e6 prev_Rmax = 1.e6 last_min_Rmax = 1.e6 iteration = 0 converged = False kT = kT_last fext = a.calc_fext(inc=total) iter_NR = 0 while True: iteration += 1 msg('Iteration: {}'.format(iteration), level=2) if iteration > a.maxNumIter: warn('Maximum number of iterations achieved!', level=2) break if compute_kT or (a.kT_initial_state and step_num==1 and iteration==1) or iter_NR==(a.compute_every_n-1): iter_NR = 0 kT = a.calc_kT(c, inc=total) else: iter_NR += 1 if not modified_NR: compute_kT = True fint = a.calc_fint(c=c, inc=total) R = fext - fint # convergence criteria: # - maximum residual force Rmax Rmax = np.abs(R).max() msg('Rmax = {0}'.format(Rmax), level=3) if iteration >= 2 and Rmax < a.absTOL: converged = True break if (Rmax > prev_Rmax and Rmax > min_Rmax and iteration > 2): warn('Diverged!', level=2) break else: min_Rmax = min(min_Rmax, Rmax) change_rate_Rmax = abs(prev_Rmax-Rmax)/abs(prev_Rmax) if (iteration > 2 and change_rate_Rmax < a.too_slow_TOL): warn('Diverged! (convergence too slow)', level=2) break prev_Rmax = Rmax msg('Solving... ', level=2) delta_c = solve(kT, R) msg('finished!', level=2) eta1 = 0. eta2 = 1. if a.line_search: msg('Performing line-search... ', level=2) iter_line_search = 0 while True: c1 = c + eta1*delta_c c2 = c + eta2*delta_c fint1 = a.calc_fint(c1, inc=total) fint2 = a.calc_fint(c2, inc=total) R1 = fext - fint1 R2 = fext - fint2 s1 = delta_c.dot(R1) s2 = delta_c.dot(R2) eta_new = (eta2-eta1)*(-s1/(s2-s1)) + eta1 eta1 = eta2 eta2 = eta_new eta2 = min(max(eta2, 0.2), 10.) if abs(eta2-eta1) < 0.01: break iter_line_search += 1 if iter_line_search == a.max_iter_line_search: eta2 = 1. warn('maxinum number of iterations', level=3) break msg('finished!', level=2) c = c + eta2*delta_c if converged: msg('Finished Load Step {} at'.format(step_num) + ' time = {0}'.format(total), level=1) a.increments.append(total) a.cs.append(c.copy()) #NOTE copy required finished = False if abs(total - 1) < 1e-3: finished = True else: factor = 1.1 if once_at_total: inc_new = min(factor*inc, a.maxInc, (1.-total)/2) else: inc_new = min(factor*inc, a.maxInc, 1.-total) msg('Changing time increment from {0} to {1}'.format( inc, inc_new), level=1) inc = inc_new total += inc total = min(1, total) step_num += 1 if finished: break if modified_NR: msg('Updating kT...', level=1) kT = a.calc_kT(c, inc=total) msg('kT updated!', level=1) compute_kT = False kT_last = kT else: max_total = max(max_total, total) while True: factor = 0.3 msg('Bisecting time increment from {0} to {1}'.format( inc, inc*factor), level=1) if abs(total -1) < 1e-3: once_at_total = True total -= inc inc *= factor if inc < a.minInc: msg('Minimum step size achieved!', level=1) break total += inc if total >= max_total: continue else: break if inc < a.minInc: msg('Stopping solver: minimum step size achieved!', level=1) break if len(a.cs)>0: c = a.cs[-1].copy() #NOTE copy required else: # means that a bisection must be done in initialInc fext = a.calc_fext(inc=inc) c = solve(k0, fext) msg('Finished Non-Linear Static Analysis') msg('at time {0}'.format(total), level=1)
def _rebuild(self): if not self.name: try: self.name = os.path.basename(__main__.__file__).split('.py')[0] except AttributeError: warn('Plate name unchanged') self.model = self.model.lower() valid_models = sorted(modelDB.db.keys()) if not self.model in valid_models: raise ValueError('ERROR - valid models are:\n ' + '\n '.join(valid_models)) # boundary conditions inf = self.inf zero = self.zero if inf > self.maxinf: warn('inf reduced to {0:1.1e4} due to the verified'.format( self.maxinf) + ' numerical instability for higher values', level=2) inf = self.maxinf if self.bc is not None: bc = self.bc.lower() if '_' in bc: # different bc for Bot, Top, Left and Right bc_Bot, bc_Top, bc_Left, bc_Right = self.bc.split('_') elif '-' in bc: # different bc for Bot, Top, Left and Right bc_Bot, bc_Top, bc_Left, bc_Right = self.bc.split('-') else: bc_Bot = bc_Top = bc_Left = bc_Right = bc bcs = dict(bc_Bot=bc_Bot, bc_Top=bc_Top, bc_Left=bc_Left, bc_Right=bc_Right) for k in bcs.keys(): sufix = k.split('_')[1] # Bot or Top if bcs[k] == 'ss1': setattr(self, 'ku' + sufix, inf) setattr(self, 'kv' + sufix, inf) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) elif bcs[k] == 'ss2': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, inf) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) elif bcs[k] == 'ss3': setattr(self, 'ku' + sufix, inf) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) elif bcs[k] == 'ss4': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) elif bcs[k] == 'cc1': setattr(self, 'ku' + sufix, inf) setattr(self, 'kv' + sufix, inf) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, inf) setattr(self, 'kphiy' + sufix, inf) elif bcs[k] == 'cc2': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, inf) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, inf) setattr(self, 'kphiy' + sufix, inf) elif bcs[k] == 'cc3': setattr(self, 'ku' + sufix, inf) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, inf) setattr(self, 'kphiy' + sufix, inf) elif bcs[k] == 'cc4': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, inf) setattr(self, 'kphix' + sufix, inf) setattr(self, 'kphiy' + sufix, inf) elif bcs[k] == 'free': setattr(self, 'ku' + sufix, zero) setattr(self, 'kv' + sufix, zero) setattr(self, 'kw' + sufix, zero) setattr(self, 'kphix' + sufix, zero) setattr(self, 'kphiy' + sufix, zero) else: txt = '"{}" is not a valid boundary condition!'.format(bc) raise ValueError(txt) if self.a is None: raise ValueError('The length a must be specified') if self.b is None: raise ValueError('The width b must be specified') if not self.laminaprops: self.laminaprops = [self.laminaprop for i in self.stack] if not self.plyts: self.plyts = [self.plyt for i in self.stack] def check_load(load, size): if load is not None: check = False if isinstance(load, np.ndarray): if load.ndim == 1: assert load.shape[0] == size return load elif type(load) in (int, float): newload = np.zeros(size, dtype=DOUBLE) newload[0] = load return newload if not check: raise ValueError('Invalid NxxTop input') else: return np.zeros(size, dtype=DOUBLE) # axial load size = self.n1+1 self.NxxTop = check_load(self.NxxTop, size) self.NxxTop_inc = check_load(self.NxxTop_inc, size) # shear xt self.NxyTop = check_load(self.NxyTop, size) self.NxyTop_inc = check_load(self.NxyTop_inc, size) # circumferential load size = self.m1+1 self.NyyLeft = check_load(self.NyyLeft, size) self.NyyLeft_inc = check_load(self.NyyLeft_inc, size) # shear tx self.NyxLeft = check_load(self.NyxLeft, size) self.NyxLeft_inc = check_load(self.NyxLeft_inc, size) # defining load components from force vectors if self.laminaprop is None: raise ValueError('laminaprop must be defined')
def lb(K, KG, tol=0, sparse_solver=True, silent=False, num_eigvalues=25, num_eigvalues_print=5): """Linear Buckling Analysis It can also be used for more general eigenvalue analyzes if `K` is the tangent stiffness matrix of a given load state. Parameters ---------- K : sparse_matrix Stiffness matrix. Should include all constant terms of the initial stress stiffness matrix, aerodynamic matrix and so forth when applicable. KG : sparse_matrix Initial stress stiffness matrix that multiplies the load multiplcator `\lambda` of the eigenvalue problem. tol : float, optional A float tolerance passsed to the eigenvalue solver. sparse_solver : bool, optional Tells if solver :func:`scipy.linalg.eigh` or :func:`scipy.sparse.linalg.eigsh` should be used. silent : bool, optional A boolean to tell whether the log messages should be printed. num_eigvalues : int, optional Number of calculated eigenvalues. num_eigvalues_print : int, optional Number of eigenvalues to print. Notes ----- The extracted eigenvalues are stored in the ``eigvals`` parameter of the ``Panel`` object and the `i^{th}` eigenvector in the ``eigvecs[:, i-1]`` parameter. """ msg('Running linear buckling analysis...', silent=silent) msg('Eigenvalue solver... ', level=2, silent=silent) k = min(num_eigvalues, KG.shape[0]-2) if sparse_solver: mode = 'cayley' try: msg('eigsh() solver...', level=3, silent=silent) eigvals, eigvecs = eigsh(A=KG, k=k, which='SM', M=K, tol=tol, sigma=1., mode=mode) msg('finished!', level=3, silent=silent) except Exception as e: warn(str(e), level=4, silent=silent) msg('aborted!', level=3, silent=silent) sizebkp = KG.shape[0] K, KG, used_cols = remove_null_cols(K, KG, silent=silent) msg('eigsh() solver...', level=3, silent=silent) eigvals, peigvecs = eigsh(A=KG, k=k, which='SM', M=K, tol=tol, sigma=1., mode=mode) msg('finished!', level=3, silent=silent) eigvecs = np.zeros((sizebkp, num_eigvalues), dtype=peigvecs.dtype) eigvecs[used_cols, :] = peigvecs else: size = KG.shape[0] K, KG, used_cols = remove_null_cols(K, KG, silent=silent) K = K.toarray() KG = KG.toarray() msg('eigh() solver...', level=3, silent=silent) eigvals, peigvecs = eigh(a=KG, b=K) msg('finished!', level=3, silent=silent) eigvecs = np.zeros((size, num_eigvalues), dtype=peigvecs.dtype) eigvecs[used_cols, :] = peigvecs[:, :num_eigvalues] eigvals = -1./eigvals eigvals = eigvals eigvecs = eigvecs msg('finished!', level=2, silent=silent) msg('first {0} eigenvalues:'.format(num_eigvalues_print), level=1, silent=silent) for eig in eigvals[:num_eigvalues_print]: msg('{0}'.format(eig), level=2, silent=silent) return eigvals, eigvecs
def freq(self, atype=4, tol=0, sparse_solver=False, silent=False, sort=True, damping=False, reduced_dof=False): """Performs a frequency analysis The following parameters of the ``AeroPistonStiffPanel`` object will affect the linear buckling analysis: ======================= ===================================== parameter description ======================= ===================================== ``num_eigenvalues`` Number of eigenvalues to be extracted ``num_eigvalues_print`` Number of eigenvalues to print after the analysis is completed ======================= ===================================== Parameters ---------- atype : int, optional Tells which analysis type should be performed: - ``1`` : considers k0, kA and kG0 - ``2`` : considers k0 and kA - ``3`` : considers k0 and kG0 - ``4`` : considers k0 only tol : float, optional A tolerance value passed to ``scipy.sparse.linalg.eigs``. sparse_solver : bool, optional Tells if solver :func:`scipy.linalg.eig` or :func:`scipy.sparse.linalg.eigs` should be used. .. note:: It is recommended ``sparse_solver=False``, because it was verified that the sparse solver becomes unstable for some cases, though the sparse solver is faster. silent : bool, optional A boolean to tell whether the log messages should be printed. sort : bool, optional Sort the output eigenvalues and eigenmodes. damping : bool, optinal If aerodynamic damping should be taken into account. reduced_dof : bool, optional Considers only the contributions of `v` and `w` to the stiffness matrix and accelerates the run. Only effective when ``sparse_solver=False``. Notes ----- The extracted eigenvalues are stored in the ``eigvals`` parameter of the ``AeroPistonStiffPanel`` object and the `i^{th}` eigenvector in the ``eigvecs[:, i-1]`` parameter. """ if not modelDB.db[self.model]['linear buckling']: msg('________________________________________________') msg('') warn('Model {0} cannot be used in linear buckling analysis!'. format(self.model)) msg('________________________________________________') msg('Running frequency analysis...', silent=silent) if atype == 1: self.calc_linear_matrices(silent=silent) elif atype == 2: self.calc_linear_matrices(silent=silent, calc_kG0=False) elif atype == 3: self.calc_linear_matrices(silent=silent, calc_kA=False) elif atype == 4: self.calc_linear_matrices(silent=silent, calc_kA=False, calc_kG0=False) msg('Eigenvalue solver... ', level=2, silent=silent) if atype == 1: K = self.k0 + self.kA + self.kG0 elif atype == 2: K = self.k0 + self.kA elif atype == 3: K = self.k0 + self.kG0 elif atype == 4: K = self.k0 M = self.kM if damping and self.cA is None: warn('Aerodynamic damping could not be calculated!', level=3, silent=silent) damping = False elif damping and self.cA is not None: if self.cA.sum() == 0j: damping = False msg('eigs() solver...', level=3, silent=silent) k = min(self.num_eigvalues, M.shape[0]-2) if sparse_solver: eigvals, eigvecs = eigs(A=M, M=K, k=k, tol=tol, which='SM', sigma=-1.) eigvals = np.sqrt(1./eigvals) # omega^2 to omega, in rad/s else: M = M.toarray() K = K.toarray() if reduced_dof: i = np.arange(M.shape[0]) take = np.column_stack((i[1::3], i[2::3])).flatten() M = M[:, take][take, :] K = K[:, take][take, :] if not damping: M = -M else: size = M.shape[0] cA = self.cA.toarray() if reduced_dof: cA = cA[:, take][take, :] I = np.identity(M.shape[0]) Z = np.zeros_like(M) M = np.row_stack((np.column_stack((I, Z)), np.column_stack((Z, -M)))) K = np.row_stack((np.column_stack((Z, -I)), np.column_stack((K, cA)))) eigvals, eigvecs = eig(a=M, b=K) if not damping: eigvals = np.sqrt(-1./eigvals) # -1/omega^2 to omega, in rad/s eigvals = eigvals else: eigvals = -1./eigvals # -1/omega to omega, in rad/s shape = eigvals.shape eigvals = eigvals[:shape[0]//2] eigvecs = eigvecs[:eigvecs.shape[0]//2, :shape[0]//2] msg('finished!', level=3, silent=silent) if sort: if damping: higher_zero = eigvals.real > 1e-6 eigvals = eigvals[higher_zero] eigvecs = eigvecs[:, higher_zero] sort_ind = np.lexsort((np.round(eigvals.imag, 1), np.round(eigvals.real, 0))) eigvals = eigvals[sort_ind] eigvecs = eigvecs[:, sort_ind] else: sort_ind = np.lexsort((np.round(eigvals.imag, 1), np.round(eigvals.real, 1))) eigvals = eigvals[sort_ind] eigvecs = eigvecs[:, sort_ind] higher_zero = eigvals.real > 1e-6 eigvals = eigvals[higher_zero] eigvecs = eigvecs[:, higher_zero] if not sparse_solver and reduced_dof: new_eigvecs = np.zeros((3*eigvecs.shape[0]//2, eigvecs.shape[1]), dtype=eigvecs.dtype) new_eigvecs[take, :] = eigvecs eigvecs = new_eigvecs self.eigvals = eigvals self.eigvecs = eigvecs msg('finished!', level=2, silent=silent) msg('first {0} eigenvalues:'.format(self.num_eigvalues_print), level=1, silent=silent) for eigval in eigvals[:self.num_eigvalues_print]: msg('{0} rad/s'.format(eigval), level=2, silent=silent) self.analysis.last_analysis = 'freq'