#!/usr/bin/env python3 # -*- coding: utf-8 -*- """ Created on Tue Feb 13 12:58:25 2018 @author: dave """ from __future__ import print_function from __future__ import division from __future__ import unicode_literals from __future__ import absolute_import from builtins import dict from io import open from builtins import zip from builtins import range from builtins import str from builtins import int from future import standard_library standard_library.install_aliases() from builtins import object import numpy as np import scipy def compute_env_of_env(envelope, dlc_list, Nx=300, Nsectors=12, Ntheta=181): """ The function computes load envelopes for given channels and a groups of load cases starting from the envelopes computed for single simulations. The output is the envelope of the envelopes of the single simulations. This total envelope is projected on defined polar directions. Parameters ---------- envelope : dict, dictionaries of interpolated envelopes of a given channel (it's important that each entry of the dictonary contains a matrix of the same dimensions). The dictonary is organized by load case dlc_list : list, list of load cases Nx : int, default=300 Number of points for the envelope interpolation Nsectors: int, default=12 Number of sectors in which the total envelope will be divided. The default is every 30deg Ntheta; int, default=181 Number of angles in which the envelope is interpolated in polar coordinates. Returns ------- envelope : array (Nsectors x 6), Total envelope projected on the number of angles defined in Nsectors. The envelope is projected in Mx and My and the other cross-sectional moments and forces are fetched accordingly (at the same time step where the corresponding Mx and My are occuring) """ # Group all the single DLCs cloud = np.zeros(((Nx+1)*len(envelope),6)) for i in range(len(envelope)): cloud[(Nx+1)*i:(Nx+1)*(i+1),:] = envelope[dlc_list[i]] # Compute total Hull of all the envelopes hull = scipy.spatial.ConvexHull(cloud[:,:2]) cc = np.append(cloud[hull.vertices,:2], cloud[hull.vertices[0],:2].reshape(1,2),axis=0) # Interpolate full envelope cc_x,cc_up,cc_low,cc_int= int_envelope(cc[:,0], cc[:,1], Nx=Nx) # Project full envelope on given direction cc_proj = proj_envelope(cc_x, cc_up, cc_low, cc_int, Nx, Nsectors, Ntheta) env_proj = np.zeros([len(cc_proj),6]) env_proj[:,:2] = cc_proj # Based on Mx and My, gather the remaining cross-sectional forces and # moments for ich in range(2, 6): s0 = np.array(cloud[hull.vertices, ich]).reshape(-1, 1) s1 = np.array(cloud[hull.vertices[0], ich]).reshape(-1, 1) s0 = np.append(s0, s1, axis=0) cc = np.append(cc, s0, axis=1) _,_,_,extra_sensor = int_envelope(cc[:,0],cc[:,ich],Nx) es = np.atleast_2d(np.array(extra_sensor[:,1])).T cc_int = np.append(cc_int,es,axis=1) for isec in range(Nsectors): ids = (np.abs(cc_int[:,0]-cc_proj[isec,0])).argmin() env_proj[isec,ich] = (cc_int[ids-1,ich]+cc_int[ids,ich]+\ cc_int[ids+1,ich])/3 return env_proj def int_envelope(ch1, ch2, Nx): """ Function to interpolate envelopes and output arrays of same length Number of points is defined by Nx + 1, where the + 1 is needed to close the curve """ upper = [] lower = [] indmax = np.argmax(ch1) indmin = np.argmin(ch1) if indmax > indmin: lower = np.array([ch1[indmin:indmax+1],ch2[indmin:indmax+1]]).T upper = np.concatenate((np.array([ch1[indmax:],ch2[indmax:]]).T, np.array([ch1[:indmin+1],ch2[:indmin+1]]).T), axis=0) else: upper = np.array([ch1[indmax:indmin+1],ch2[indmax:indmin+1]]).T lower = np.concatenate((np.array([ch1[indmin:],ch2[indmin:]]).T, np.array([ch1[:indmax+1],ch2[:indmax+1]]).T), axis=0) int_1 = np.linspace(min(upper[:,0].min(),lower[:,0].min()), max(upper[:,0].max(),lower[:,0].max()),Nx/2+1) upper = np.flipud(upper) int_2_up = np.interp(int_1,np.array(upper[:,0]),np.array(upper[:,1])) int_2_low = np.interp(int_1,np.array(lower[:,0]),np.array(lower[:,1])) int_env = np.concatenate((np.array([int_1[:-1],int_2_up[:-1]]).T, np.array([int_1[::-1],int_2_low[::-1]]).T), axis=0) return int_1, int_2_up, int_2_low, int_env def proj_envelope(env_x, env_up, env_low, env, Nx, Nsectors, Ntheta): """ Function to project envelope on given angles Angles of projection is defined by Nsectors Projections are obtained in polar coordinates and outputted in cartesian """ theta_int = np.linspace(-np.pi,np.pi,Ntheta) sectors = np.linspace(-np.pi,np.pi,Nsectors+1) proj = np.zeros([Nsectors,2]) R_up = np.sqrt(env_x**2+env_up**2) theta_up = np.arctan2(env_up,env_x) R_low = np.sqrt(env_x**2+env_low**2) theta_low = np.arctan2(env_low,env_x) R = np.concatenate((R_up,R_low)) theta = np.concatenate((theta_up,theta_low)) R = R[np.argsort(theta)] theta = np.sort(theta) R_int = np.interp(theta_int,theta,R,period=2*np.pi) for i in range(Nsectors): if sectors[i]>=-np.pi and sectors[i+1]<-np.pi/2: indices = np.where(np.logical_and(theta_int >= sectors[i], theta_int <= sectors[i+1])) maxR = R_int[indices].max() proj[i+1,0] = maxR*np.cos(sectors[i+1]) proj[i+1,1] = maxR*np.sin(sectors[i+1]) elif sectors[i]==-np.pi/2: continue elif sectors[i]>-np.pi/2 and sectors[i+1]<=0: indices = np.where(np.logical_and(theta_int >= sectors[i], theta_int <= sectors[i+1])) maxR = R_int[indices].max() proj[i,0] = maxR*np.cos(sectors[i]) proj[i,1] = maxR*np.sin(sectors[i]) elif sectors[i]>=0 and sectors[i+1]<np.pi/2: indices = np.where(np.logical_and(theta_int >= sectors[i], theta_int <= sectors[i+1])) maxR = R_int[indices].max() proj[i+1,0] = maxR*np.cos(sectors[i+1]) proj[i+1,1] = maxR*np.sin(sectors[i+1]) elif sectors[i]==np.pi/2: continue elif sectors[i]>np.pi/2 and sectors[i+1]<=np.pi: indices = np.where(np.logical_and(theta_int >= sectors[i], theta_int <= sectors[i+1])) maxR = R_int[indices].max() proj[i,0] = maxR*np.cos(sectors[i]) proj[i,1] = maxR*np.sin(sectors[i]) ind = np.where(sectors==0) proj[ind,0] = env[:,0].max() ind = np.where(sectors==np.pi/2) proj[ind,1] = env[:,1].max() ind = np.where(sectors==-np.pi) proj[ind,0] = env[:,0].min() ind = np.where(sectors==-np.pi/2) proj[ind,1] = env[:,1].min() return proj def closed_contour(cloud): """Return indices of the vertices of the closed convex contour that contain all points of the (x,y) cloud of pairs. Parameters ---------- cloud : ndarray of floats, shape (npoints, ndim) Coordinates of points to construct a convex hull from. Ndim should be at least 2 or higher. cloud_extra : np.ndarray(npoints, nr_extra_channels) Returns ------- ivertices : ndarray(nvertices) vertices : ndarray(nvertices, 2+nr_extra_channels) """ if not cloud.shape[1] >= 2: raise IndexError('Cloud dimension should be 2 or greater') hull = scipy.spatial.ConvexHull(cloud[:,0:2]) # indices to the vertices containing the cloud of the first 2 dimensions ivertices = np.ndarray((len(hull.vertices)+1,), dtype=np.int32) ivertices[0:-1] = hull.vertices ivertices[-1] = hull.vertices[0] # actual vertices of all dimensions in the cloud, based on the first two # dimensions vertices = np.ndarray((len(ivertices), cloud.shape[1])) vertices[0:-1,:] = cloud[ivertices[0:-1],:] vertices[-1,:] = cloud[ivertices[-1],:] return vertices, ivertices def compute_envelope(cloud, int_env=False, Nx=300): """ The function computes load envelopes for given signals and a single load case. Starting from Mx and My moments, the other cross-sectional forces are identified. Parameters ---------- x, y : np.ndarray(n) 2 components of the same time signal, e.g x and y. int_env : boolean, default=False If the logic parameter is True, the function will interpolate the envelope on a given number of points Nx : int, default=300 Number of points for the envelope interpolation Returns ------- envelope : dictionary, The dictionary has entries refered to the channels selected. Inside the dictonary under each entry there is a matrix with 6 columns, each for the sectional forces and moments """ vertices, ivertices = closed_contour(cloud) # interpolate to a fixed location of equally spaced vertices if int_env: vert_int = np.ndarray((Nx+1, cloud.shape[1])) _,_,_,vert_int[:,0:2] = int_envelope(vertices[:,0], vertices[:,1], Nx) for i in range(2, cloud.shape[1]): _,_,_,extra = int_envelope(vertices[:,0], vertices[:,i], Nx) vert_int[:,i] = extra[:,1] vertices = vert_int return vertices