def panel_positions(self, DSTEP1, DSTEP2, DSTEP3, T, THETA, HEAVE): """Updates all the absolute-frame coordinates of the body. Args: DSTEP: Small incremental distance to pass into neutral_plane(). T: Time of current step. THETA: Current pitching angle. """ bfx = self.BF.x bfy = self.BF.y #No y information coming from the geometry class yet bfz = self.BF.z bfz_col = self.BF.z_col V0 = self.V0 # Used only for x_le (x_neut, y_neut, z_neut) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE) # Infinitesimal differences on the neutral axis to calculate the tangential and normal vectors v1 = (xdp_y, ydp_y, zdp_y) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE, 0, DSTEP2, DSTEP3) v2 = (xdm_y, ydm_y, zdm_y) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE, 0, -DSTEP2, -DSTEP3) v3 = (xdp_x, ydp_x, zdp_x) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE, DSTEP1, 0, 0) v4 = (xdm_x, ydm_x, zdm_x) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE, -DSTEP1, 0, 0) # Absolute-frame panel endpoint positions for time t afx = x_neut + point_vectors(v1,v2,v3,v4)[1]*bfz afy = y_neut + point_vectors(v1,v2,v3,v4)[2]*bfz afz = z_neut + point_vectors(v1,v2,v3,v4)[3]*bfz # Absolute-frame panel midpoint positions x_mid = (afx[1:,:-1]+afx[:-1,:-1])/2 y_mid = (afy[1:,:-1]+afy[:-1,:-1])/2 z_mid = (afz[1:,:-1]+afz[:-1,:-1])/2 # Collocation points are the points where impermeable boundary condition is forced # They should be shifted inside or outside of the boundary depending on the dirichlet or neumann condition # Shifting surface collocation points some percent of the height from the neutral axis # Normal vectors point outward but positive S is inward, so the shift must be subtracted from the panel midpoints afx_col = x_mid - self.S*panel_vectors(afx, afz)[2]*np.absolute(bfz_col) afy_col = y_mid - self.S*panel_vectors(afx, afz)[2]*np.absolute(bfz_col) afz_col = z_mid - self.S*panel_vectors(afx, afz)[3]*np.absolute(bfz_col) self.AF.x = afx self.AF.y = afy self.AF.z = afz self.AF.x_col = afx_col self.AF.y_col = afy_col self.AF.z_col = afz_col self.AF.x_mid = x_mid self.AF.y_mid = y_mid self.AF.z_mid = z_mid self.AF.x_neut = x_neut self.AF.y_neut = y_neut self.AF.z_neut = z_neut # Location of leading edge (currently pitching motion only) self.AF.x_le = V0*T self.AF.y_le = 0 self.AF.z_le = HEAVE
def pressure(self, RHO, DEL_T, i): """Calculates the pressure distribution along the body's surface. Args: RHO: Fluid density. DEL_T: Time step length. i: Time step number. """ (tx,tz,nx,nz,lpanel) = panel_vectors(self.AF.x,self.AF.z) # Tangential panel velocity dmu/dl, first-order differencing dmu_dl = np.empty(self.N) dmu_dl[0] = (self.mu[0]-self.mu[1]) / (lpanel[0]/2 + lpanel[1]/2) dmu_dl[1:-1] = (self.mu[:-2]-self.mu[2:]) / (lpanel[:-2]/2 + lpanel[1:-1] + lpanel[2:]/2) dmu_dl[-1] = (self.mu[-2]-self.mu[-1]) / (lpanel[-2]/2 + lpanel[-1]/2) # Potential change dmu/dt, second-order differencing after first time step if i == 0: dmu_dt = self.mu / DEL_T if i == 1: dmu_dt = (self.mu - self.mu_past[0,:])/DEL_T else: dmu_dt = (3*self.mu - 4*self.mu_past[0,:] + self.mu_past[1,:])/(2*DEL_T) # Unsteady pressure calculation (from Matlab code) qpx_tot = dmu_dl*tx + self.sigma*nx qpz_tot = dmu_dl*tz + self.sigma*nz self.p = -RHO*(qpx_tot**2 + qpz_tot**2)/2. + RHO*dmu_dt + RHO*(qpx_tot*(self.V0+self.vx) + qpz_tot*self.vz) self.cp = self.p / (0.5*RHO*self.V0**2)
def fsi_panel_positions(self, FSI, T, THETA, HEAVE): self.AF.x = self.AF.x + (FSI.fluidNodeDispl[:,0] - FSI.fluidNodeDisplOld[:,0]) self.AF.z = self.AF.z + (FSI.fluidNodeDispl[:,1] - FSI.fluidNodeDisplOld[:,1]) self.AF.x_mid[0,:] = (self.AF.x[:-1] + self.AF.x[1:])/2 self.AF.z_mid[0,:] = (self.AF.z[:-1] + self.AF.z[1:])/2 self.BF.x = (self.AF.x - self.AF.x_le) * np.cos(-1*THETA) - (self.AF.z - self.AF.z_le) * np.sin(-1*THETA) self.BF.z = (self.AF.z - self.AF.z_le) * np.cos(-1*THETA) + (self.AF.x - self.AF.x_le) * np.sin(-1*THETA) self.BF.x_col = ((self.BF.x[1:] + self.BF.x[:-1])/2) self.BF.z_col = ((self.BF.z[1:] + self.BF.z[:-1])/2) (self.AF.x_neut, self.AF.z_neut) = self.neutral_axis(self.BF.x, T, THETA, HEAVE) self.AF.x_col = self.AF.x_mid[0,:] - self.S*panel_vectors(self.AF.x, self.AF.z)[2]*np.absolute(self.zcval) #self.BF.z_col self.AF.z_col = self.AF.z_mid[0,:] - self.S*panel_vectors(self.AF.x, self.AF.z)[3]*np.absolute(self.zcval)
def force(self, THETA, RHO, V0, C, B, i): """Calculates drag and lift forces acting on the body. Args: i: Time step number. """ (tx,tz,nx,nz,lpanel) = panel_vectors(self.AF.x, self.AF.z) delFx = -self.p * lpanel * B * nx delFz = -self.p * lpanel * B * nz delF = np.array([delFx, delFz]) # delF = -np.multiply(np.kron(self.p, np.array([1, 1])), np.array([nx, nz]).T) delP = np.sum(-delF * np.array([self.vx.T, self.vz.T]), 1) force = np.sum(delF,1) lift = force[1] * np.cos(THETA) - force[0] * np.sin(THETA) thrust = -(force[1] * np.sin(THETA) + force[0] * np.cos(THETA)) power = np.sum(delP, 0) self.Cf = np.sqrt(force[0]**2 + force[1]**2) / (0.5 * RHO * V0**2 * C *B) self.Cl = lift /(0.5 * RHO * V0**2 * C *B) self.Ct = thrust / (0.5 * RHO * V0**2 * C *B) self.Cpow = power / (0.5 * RHO * V0**3 * C *B) # Body.drag[i-1] = np.dot(self.p[i-1,:]*lpanel, np.reshape(tx,(self.N,1)))\ # + np.dot(self.p[i-1,:]*lpanel, np.reshape(-tz,(self.N,1))) # # self.lift[i-1] = np.dot(self.p[i-1,:]*lpanel, np.reshape(-nz,(self.N,1)))\ # + np.dot(self.p[i-1,:]*lpanel, np.reshape(nx,(self.N,1)))
def panel_positions(self, DSTEP, T): """Updates all the absolute-frame coordinates of the body. Args: DSTEP: Small incremental distance to pass into neutral_axis(). T: Time of current step. bfx, bfz: Body-frame x- and z-coordinates. bfz_col: Body-frame collocation z-coordinates (unshifted) V0: Free-stream velocity. """ bfx = self.BF.x bfz = self.BF.z bfz_col = self.BF.z_col V0 = self.V0 # Used only for x_le (x_neut, z_neut) = self.neutral_axis(bfx, T) # Infinitesimal differences on the neutral axis to calculate the tangential and normal vectors (xdp_s, zdp_s) = self.neutral_axis(bfx, T, DSTEP) (xdm_s, zdm_s) = self.neutral_axis(bfx, T, -DSTEP) # Absolute-frame panel endpoint positions for time t afx = x_neut + point_vectors(xdp_s, xdm_s, zdp_s, zdm_s)[2]*bfz afz = z_neut + point_vectors(xdp_s, xdm_s, zdp_s, zdm_s)[3]*bfz # Absolute-frame panel midpoint positions x_mid = (afx[:-1]+afx[1:])/2 z_mid = (afz[:-1]+afz[1:])/2 # Collocation points are the points where impermeable boundary condition is forced # They should be shifted inside or outside of the boundary depending on the dirichlet or neumann condition # Shifting surface collocation points some percent of the height from the neutral axis # Normal vectors point outward but positive S is inward, so the shift must be subtracted from the panel midpoints afx_col = x_mid - self.S*panel_vectors(afx, afz)[2]*np.absolute(bfz_col) afz_col = z_mid - self.S*panel_vectors(afx, afz)[3]*np.absolute(bfz_col) self.AF.x = afx self.AF.z = afz self.AF.x_col = afx_col self.AF.z_col = afz_col self.AF.x_mid[0,:] = x_mid self.AF.z_mid[0,:] = z_mid self.AF.x_neut = x_neut self.AF.z_neut = z_neut # Location of leading edge (assuming pitching motion only) self.AF.x_le = V0*T self.AF.z_le = 0.
def edge_shed(self, DEL_T, i): """Updates the position of the Edge panel. This should only be done once each time step for a swimmer. The edge panel's length is based on Edge.CE. The strength of this panel is solved for in kutta(). Args: DEL_T: Time step length. i: Time step number. Body: Body of the swimmer. Edge: Edge panel of separation. """ Body = self.Body Edge = self.Edge Edge.x[0] = Body.AF.x[0] Edge.z[0] = Body.AF.z[0] Edge.x[1] = Body.AF.x[0] + Edge.CE*panel_vectors(Body.AF.x_neut,Body.AF.z_neut)[0][0]*Body.V0*DEL_T Edge.z[1] = Body.AF.z[0] + Edge.CE*panel_vectors(Body.AF.x_neut,Body.AF.z_neut)[1][0]*Body.V0*DEL_T
def s2f(Solid, tempNodes, SW_INTERP_MTD): # Build arrays containing the new fluid panel node positions. newxp = np.zeros_like(Solid.meanline_p0) newzp = np.zeros_like(Solid.meanline_p0) (tx,tz,nx,nz,lelem) = panel_vectors(tempNodes[:,0], tempNodes[:,1]) for i in xrange(Solid.xp_0.shape[0]): assoElem = Solid.elemAsso[i] percent = (Solid.meanline_p0[i] - Solid.nodes[assoElem,2]) / (Solid.nodes[assoElem+1,2] - Solid.nodes[assoElem,2]) newxp[i] = tempNodes[assoElem,0] + percent * lelem[assoElem] * tx[assoElem] + Solid.zp_0[i] * nx[assoElem] newzp[i] = tempNodes[assoElem,1] + percent * lelem[assoElem] * tz[assoElem] + Solid.zp_0[i] * nz[assoElem] return (newxp, newzp)
def surface_kinematics(self, DSTEP, TSTEP, THETA_MINUS, THETA_PLUS, HEAVE_MINUS, HEAVE_PLUS, DEL_T, T, i): """Calculates the body-frame surface velocities of body panels. Also finds the body panel source strengths based on these surface velocities. Args: DSTEP, TSTEP: Incremental distance/time passed into neutral_axis(). DEL_T: Time step length. T: Time of current step. i: Time step number. THETA_MINUS: Pitching angle minus a small time difference (TSTEP) THETA_PLUS: Pitching angle plus a small time difference (TSTEP) """ if i == 0: x_col = self.BF.x_col z_col = self.BF.z_col # Panel midpoint velocity calculations # Calculating the surface positions at tplus(tp) and tminus(tm) (xtpneut, ztpneut) = self.neutral_axis(x_col, T, THETA_PLUS, HEAVE_PLUS, 0) (xtpdp, ztpdp) = self.neutral_axis(x_col, T, THETA_PLUS, HEAVE_PLUS, DSTEP) (xtpdm, ztpdm) = self.neutral_axis(x_col, T, THETA_PLUS, HEAVE_PLUS, -DSTEP) (xtmneut, ztmneut) = self.neutral_axis(x_col, T, THETA_MINUS, HEAVE_MINUS, 0) (xtmdp, ztmdp) = self.neutral_axis(x_col, T, THETA_MINUS, HEAVE_MINUS, DSTEP) (xtmdm, ztmdm) = self.neutral_axis(x_col, T, THETA_MINUS, HEAVE_MINUS, -DSTEP) # Displaced airfoil's panel midpoints for times tplus(tp) and tminus(tm) xctp = xtpneut + point_vectors(xtpdp, xtpdm, ztpdp, ztpdm)[2]*z_col xctm = xtmneut + point_vectors(xtmdp, xtmdm, ztmdp, ztmdm)[2]*z_col zctp = ztpneut + point_vectors(xtpdp, xtpdm, ztpdp, ztpdm)[3]*z_col zctm = ztmneut + point_vectors(xtmdp, xtmdm, ztmdp, ztmdm)[3]*z_col # Velocity calculations on the surface panel midpoints self.vx = (xctp - xctm)/(2*TSTEP) self.vz = (zctp - zctm)/(2*TSTEP) elif i == 1: # First-order backwards differencing of body collocation point positions self.vx = (self.AF.x_mid[0,:]-self.AF.x_mid[1,:])/DEL_T - self.V0 self.vz = (self.AF.z_mid[0,:]-self.AF.z_mid[1,:])/DEL_T else: # Second-order backwards differencing of body collocation point positions self.vx = (3*self.AF.x_mid[0,:]-4*self.AF.x_mid[1,:]+self.AF.x_mid[2,:])/(2*DEL_T) - self.V0 self.vz = (3*self.AF.z_mid[0,:]-4*self.AF.z_mid[1,:]+self.AF.z_mid[2,:])/(2*DEL_T) # Body source strengths with normal vector pointing outward (overall sigma pointing outward) (nx,nz) = panel_vectors(self.AF.x,self.AF.z)[2:4] self.sigma = nx*(self.V0 + self.vx) + nz*self.vz
def force(self, i): """Calculates drag and lift forces acting on the body. Args: i: Time step number. """ (tx,tz,nx,nz,lpanel) = panel_vectors(self.AF.x, self.AF.z) Body.drag[i-1] = np.dot(self.p[i-1,:]*lpanel, np.reshape(tx,(self.N,1)))\ + np.dot(self.p[i-1,:]*lpanel, np.reshape(-tz,(self.N,1))) self.lift[i-1] = np.dot(self.p[i-1,:]*lpanel, np.reshape(-nz,(self.N,1)))\ + np.dot(self.p[i-1,:]*lpanel, np.reshape(nx,(self.N,1)))
def s2f(Solid, tempNodes, SW_INTERP_MTD): # Build arrays containing the new fluid panel node positions. newxp = np.zeros_like(Solid.meanline_p0) newzp = np.zeros_like(Solid.meanline_p0) (tx, tz, nx, nz, lelem) = panel_vectors(tempNodes[:, 0], tempNodes[:, 1]) for i in xrange(Solid.xp_0.shape[0]): assoElem = Solid.elemAsso[i] percent = (Solid.meanline_p0[i] - Solid.nodes[assoElem, 2]) / ( Solid.nodes[assoElem + 1, 2] - Solid.nodes[assoElem, 2]) newxp[i] = tempNodes[assoElem, 0] + percent * lelem[assoElem] * tx[ assoElem] + Solid.zp_0[i] * nx[assoElem] newzp[i] = tempNodes[assoElem, 1] + percent * lelem[assoElem] * tz[ assoElem] + Solid.zp_0[i] * nz[assoElem] return (newxp, newzp)
def surface_kinematics(self, DSTEP, TSTEP, DEL_T, T, i): """Calculates the body-frame surface velocities of body panels. Args: DSTEP, TSTEP: Incremental distance/time passed into neutral_axis(). DEL_T: Time step length. T: Time of current step. i: Time step number. x_col, z_col: Unshifted body-frame collocation point coordinates. """ if i == 0: x_col = self.BF.x_col z_col = self.BF.z_col # Panel midpoint velocity calculations # Calculating the surface positions at tplus(tp) and tminus(tm) (xtpneut, ztpneut) = self.neutral_axis(x_col, T, 0, TSTEP) (xtpdp, ztpdp) = self.neutral_axis(x_col, T, DSTEP, TSTEP) (xtpdm, ztpdm) = self.neutral_axis(x_col, T, -DSTEP, TSTEP) (xtmneut, ztmneut) = self.neutral_axis(x_col, T, 0, -TSTEP) (xtmdp, ztmdp) = self.neutral_axis(x_col, T, DSTEP, -TSTEP) (xtmdm, ztmdm) = self.neutral_axis(x_col, T, -DSTEP, -TSTEP) # Displaced airfoil's panel midpoints for times tplus(tp) and tminus(tm) xctp = xtpneut + point_vectors(xtpdp, xtpdm, ztpdp, ztpdm)[2]*z_col xctm = xtmneut + point_vectors(xtmdp, xtmdm, ztmdp, ztmdm)[2]*z_col zctp = ztpneut + point_vectors(xtpdp, xtpdm, ztpdp, ztpdm)[3]*z_col zctm = ztmneut + point_vectors(xtmdp, xtmdm, ztmdp, ztmdm)[3]*z_col # Velocity calculations on the surface panel midpoints self.vx = (xctp - xctm)/(2*TSTEP) self.vz = (zctp - zctm)/(2*TSTEP) elif i == 1: # First-order backwards differencing of body collocation point positions self.vx = (self.AF.x_mid[0,:]-self.AF.x_mid[1,:])/DEL_T - self.V0 self.vz = (self.AF.z_mid[0,:]-self.AF.z_mid[1,:])/DEL_T else: # Second-order backwards differencing of body collocation point positions self.vx = (3*self.AF.x_mid[0,:]-4*self.AF.x_mid[1,:]+self.AF.x_mid[2,:])/(2*DEL_T) - self.V0 self.vz = (3*self.AF.z_mid[0,:]-4*self.AF.z_mid[1,:]+self.AF.z_mid[2,:])/(2*DEL_T) (nx,nz) = panel_vectors(self.AF.x,self.AF.z)[2:4] self.sigma = nx*(self.V0 + self.vx) + nz*self.vz
def body_plot(Swimmers, SW_PLOT_FIG): global n_fig if SW_PLOT_FIG: figure = plt.figure(1) ax = figure.gca(projection='3d') # ax.set_aspect('equal') This feature has not been implemented in 3D plotting yet figure.set_size_inches(16, 9) plt.tick_params(labelsize=28) for Swim in Swimmers: (nx, ny, nz, txs, tys, tzs, txc, tyc, tzc, lps, lpc) = panel_vectors(Swim.Body.AF.x, Swim.Body.AF.y, Swim.Body.AF.z) x = Swim.Body.AF.x_mid[::5,::5,0] + 0.1 * nx[::5,::5] y = Swim.Body.AF.y_mid[::5,::5,0] + 0.1 * ny[::5,::5] z = Swim.Body.AF.z_mid[::5,::5,0] + 0.1 * nz[::5,::5] nx = nx[::5,::5] ny = ny[::5,::5] nz = nz[::5,::5] ax.quiver(x, y, z, nx, ny, nz, length=0.1, color='r') ax.quiver(Swim.Body.AF.x_mid[::5,::5,0], Swim.Body.AF.y_mid[::5,::5,0], Swim.Body.AF.z_mid[::5,::5,0], txs[::5,::5], tys[::5,::5], tzs[::5,::5], length=0.1, color='g') ax.quiver(Swim.Body.AF.x_mid[::5,::5,0], Swim.Body.AF.y_mid[::5,::5,0], Swim.Body.AF.z_mid[::5,::5,0], txc[::5,::5], tyc[::5,::5], tzc[::5,::5], length=0.1, color='b') ax.plot_surface(Swim.Body.AF.x, Swim.Body.AF.y, Swim.Body.AF.z, rstride=1, cstride=1, linewidth=0, color='k', antialiased=True) # Determine if the output directory exists. If not, create the directory. if not os.path.exists('./movies'): os.makedirs('./movies') ax.set_xlim(np.min(Swim.Body.AF.x)-1.25, np.min(Swim.Body.AF.x)+1.25) ax.set_ylim(-1.25, 1.25) ax.set_zlim(-1.25, 1.25) ax.view_init(elev=18, azim=-124) ax.set_xlabel('$X$ $[m]$', fontsize=28) ax.set_ylabel('$Y$ $[m]$', fontsize=28) ax.set_zlabel('$Z$ $[m]$', fontsize=28) ax.xaxis._axinfo['label']['space_factor'] = 2.0 ax.yaxis._axinfo['label']['space_factor'] = 2.0 ax.zaxis._axinfo['label']['space_factor'] = 2.0 figure.savefig('./movies/%05i.png' % (n_fig), format='png') plt.clf() n_fig += 1
def force(self, THETA, RHO, V0, C, B, i, SW_SV_FORCES): """Calculates drag and lift forces acting on the body. Args: i: Time step number. """ (tx,tz,nx,nz,lpanel) = panel_vectors(self.AF.x, self.AF.z) delFx = -self.p * lpanel * B * nx delFz = -self.p * lpanel * B * nz delF = np.array([delFx, delFz]) delP = np.sum(-delF * np.array([self.vx.T, self.vz.T]), 1) force = np.sum(delF,1) lift = force[1] thrust = -force[0] # lift = force[1] * np.cos(THETpythonforce[0] * np.cos(THETA)) power = np.sum(delP, 0) self.Cf = np.sqrt(force[0]**2 + force[1]**2) / (0.5 * RHO * np.abs(V0)**2 * C * B) self.Cl = lift /(0.5 * RHO * np.abs(V0)**2 * C * B) self.Ct = thrust / (0.5 * RHO * np.abs(V0)**2 * C * B) self.Cpow = power / (0.5 * RHO * np.abs(V0)**3 * C * B)
def force(self, THETA, RHO, V0, C, B, i): """Calculates drag, lift, and thrust forces acting on the body. Calculates input power. Args: i: Time step number. """ (tx,tz,nx,nz,lpanel) = panel_vectors(self.AF.x, self.AF.z) delFx = -self.p * lpanel * B * nx delFz = -self.p * lpanel * B * nz delF = np.array([delFx, delFz]) delP = np.sum(-delF * np.array([self.vx.T, self.vz.T]), 1) force = np.sum(delF,1) lift = force[1] * np.cos(THETA) - force[0] * np.sin(THETA) thrust = -(force[1] * np.sin(THETA) + force[0] * np.cos(THETA)) power = np.sum(delP, 0) self.Cf = np.sqrt(force[0]**2 + force[1]**2) / (0.5 * RHO * V0**2 * C *B) self.Cl = lift /(0.5 * RHO * V0**2 * C *B) self.Ct = thrust / (0.5 * RHO * V0**2 * C *B) self.Cpow = power / (0.5 * RHO * V0**3 * C *B)
def wake_rollup(Swimmers, DEL_T, i, P): """Performs wake rollup on the swimmers' wake panels. Args: Swimmers: List of Swimmer objects being simulated. DEL_T: Time step length. i: Time step number. """ if (P['SW_ROLLUP']): # Wake panels initialize when i==1 if i == 0: pass else: NT = i # Number of targets (wake panel points that are rolling up) for SwimT in Swimmers: SwimT.Wake.vx = np.zeros(NT) SwimT.Wake.vz = np.zeros(NT) DELTA_CORE = SwimT.DELTA_CORE for SwimI in Swimmers: # Coordinate transformation for body panels influencing wake (xp1, xp2, zp) = transformation(SwimT.Wake.x[1:i + 1], SwimT.Wake.z[1:i + 1], SwimI.Body.AF.x, SwimI.Body.AF.z) # Angle of normal vector with respect to global z-axis (nx, nz) = panel_vectors(SwimI.Body.AF.x, SwimI.Body.AF.z)[2:4] beta = np.arctan2(-nx, nz) # Katz-Plotkin eqns 10.20 and 10.21 for body source influence dummy1 = np.log( (xp1**2 + zp**2) / (xp2**2 + zp**2)) / (4 * np.pi) dummy2 = (np.arctan2(zp, xp2) - np.arctan2(zp, xp1)) / (2 * np.pi) # Rotate back to global coordinates dummy3 = dummy1 * np.cos(beta) - dummy2 * np.sin(beta) dummy4 = dummy1 * np.sin(beta) + dummy2 * np.cos(beta) # Finish eqns 10.20 and 10.21 for induced velocity by multiplying with sigma SwimT.Wake.vx += np.dot(dummy3, SwimI.Body.sigma) SwimT.Wake.vz += np.dot(dummy4, SwimI.Body.sigma) # Formation of (x-x0) and (z-z0) matrices, similar to xp1/xp2/zp but coordinate transformation is not necessary NI = SwimI.Body.N + 1 xp = np.repeat(SwimT.Wake.x[1:i + 1, np.newaxis], NI, 1) - np.repeat( SwimI.Body.AF.x[:, np.newaxis].T, NT, 0) zp = np.repeat(SwimT.Wake.z[1:i + 1, np.newaxis], NI, 1) - np.repeat( SwimI.Body.AF.z[:, np.newaxis].T, NT, 0) # Find distance r_b between each influence/target r_b = np.sqrt(xp**2 + zp**2) # Katz-Plotkin eqns 10.9 and 10.10 for body doublet (represented as point vortices) influence dummy1 = zp / (2 * np.pi * (r_b**2 + DELTA_CORE**2)) dummy2 = -xp / (2 * np.pi * (r_b**2 + DELTA_CORE**2)) # Finish eqns 10.9 and 10.10 by multiplying with Body.gamma, add to induced velocity SwimT.Wake.vx += np.dot(dummy1, SwimI.Body.gamma) SwimT.Wake.vz += np.dot(dummy2, SwimI.Body.gamma) # Formation of (x-x0) and (z-z0) matrices, similar to xp1/xp2/zp but coordinate transformation is not necessary NI = SwimI.Edge.N + 1 xp = np.repeat(SwimT.Wake.x[1:i + 1, np.newaxis], NI, 1) - np.repeat( SwimI.Edge.x[:, np.newaxis].T, NT, 0) zp = np.repeat(SwimT.Wake.z[1:i + 1, np.newaxis], NI, 1) - np.repeat( SwimI.Edge.z[:, np.newaxis].T, NT, 0) # Find distance r_e between each influence/target r_e = np.sqrt(xp**2 + zp**2) # Katz-Plotkin eqns 10.9 and 10.10 for edge (as point vortices) influence dummy1 = zp / (2 * np.pi * (r_e**2 + DELTA_CORE**2)) dummy2 = -xp / (2 * np.pi * (r_e**2 + DELTA_CORE**2)) # Finish eqns 10.9 and 10.10 by multiplying with Edge.gamma, add to induced velocity SwimT.Wake.vx += np.dot(dummy1, SwimI.Edge.gamma) SwimT.Wake.vz += np.dot(dummy2, SwimI.Edge.gamma) # Formation of (x-x0) and (z-z0) matrices, similar to xp1/xp2/zp but coordinate transformation is not necessary NI = i + 1 xp = np.repeat( SwimT.Wake.x[1:i + 1, np.newaxis], NI, 1) - np.repeat( SwimI.Wake.x[:i + 1, np.newaxis].T, NT, 0) zp = np.repeat( SwimT.Wake.z[1:i + 1, np.newaxis], NI, 1) - np.repeat( SwimI.Wake.z[:i + 1, np.newaxis].T, NT, 0) # Find distance r_w between each influence/target r_w = np.sqrt(xp**2 + zp**2) # Katz-Plotkin eqns 10.9 and 10.10 for wake (as point vortices) influence dummy1 = zp / (2 * np.pi * (r_w**2 + DELTA_CORE**2)) dummy2 = -xp / (2 * np.pi * (r_w**2 + DELTA_CORE**2)) # Finish eqns 10.9 and 10.10 by multiplying with Wake.gamma, add to induced velocity SwimT.Wake.vx += np.dot(dummy1, SwimI.Wake.gamma[:i + 1]) SwimT.Wake.vz += np.dot(dummy2, SwimI.Wake.gamma[:i + 1]) for Swim in Swimmers: # Modify wake with the total induced velocity Swim.Wake.x[1:i + 1] += Swim.Wake.vx * DEL_T Swim.Wake.z[1:i + 1] += Swim.Wake.vz * DEL_T
def surface_kinematics(self, P, i): """Calculates the body-frame surface velocities of body panels. Also finds the body panel source strengths based on these surface velocities. Args: DSTEP, TSTEP: Incremental distance/time passed into neutral_axis(). DEL_T: Time step length. T: Time of current step. i: Time step number. THETA_MINUS: Pitching angle minus a small time difference (TSTEP) THETA_PLUS: Pitching angle plus a small time difference (TSTEP) """ TSTEP = P['TSTEP'] THETA_MINUS = P['THETA_MINUS'][i] THETA_PLUS = P['THETA_PLUS'][i] HEAVE_MINUS = P['HEAVE_MINUS'][i] HEAVE_PLUS = P['HEAVE_PLUS'][i] DEL_T = P['DEL_T'] T = P['T'][i] if i == 0: # Use the prescribed kinematics to do a central difference over a # small period of time x_col = self.BF.x_col z_col = self.BF.z_col xp = x_col * np.cos(THETA_PLUS) - z_col * np.sin( THETA_PLUS) + self.V * (T + TSTEP) zp = x_col * np.sin(THETA_PLUS) + z_col * np.cos( THETA_PLUS) + HEAVE_PLUS xm = x_col * np.cos(THETA_MINUS) - z_col * np.sin( THETA_MINUS) + self.V * (T - TSTEP) zm = x_col * np.sin(THETA_MINUS) + z_col * np.cos( THETA_MINUS) + HEAVE_MINUS self.vx = (xp - xm) / (2. * TSTEP) - self.V self.vz = (zp - zm) / (2. * TSTEP) elif i == 1: # First-order backwards differencing of body collocation point positions self.vx = (self.AF.x_mid[0, :] - self.AF.x_mid[1, :]) / DEL_T - self.V self.vz = (self.AF.z_mid[0, :] - self.AF.z_mid[1, :]) / DEL_T elif i == 2 or i == 3: # Second-order backwards differencing of body collocation point positions self.vx = (3 * self.AF.x_mid[0, :] - 4 * self.AF.x_mid[1, :] + self.AF.x_mid[2, :]) / (2 * DEL_T) - self.V self.vz = (3 * self.AF.z_mid[0, :] - 4 * self.AF.z_mid[1, :] + self.AF.z_mid[2, :]) / (2 * DEL_T) else: # Fourth-order backwards differencing of body collocation point positions self.vx = (25 / 12 * self.AF.x_mid[0, :] - 4 * self.AF.x_mid[1, :] + 3 * self.AF.x_mid[2, :] - 4 / 3 * self.AF.x_mid[3, :] + 1 / 4 * self.AF.x_mid[4, :]) / DEL_T - self.V self.vz = (25 / 12 * self.AF.z_mid[0, :] - 4 * self.AF.z_mid[1, :] + 3 * self.AF.z_mid[2, :] - 4 / 3 * self.AF.z_mid[3, :] + 1 / 4 * self.AF.z_mid[4, :]) / DEL_T # Body source strengths with normal vector pointing outward (overall sigma pointing outward) (nx, nz) = panel_vectors(self.AF.x, self.AF.z)[2:4] self.sigma = nx * (self.V + self.vx) + nz * self.vz
def setInterfaceForce(self, Solid, Body, PyFEA, THETA, HEAVE, outerCorr, SW_VISC_DRAG, delFs, SW_INTERP_MTD, C, i_t): """ Updates the structural mesh position, calculates the traction forces on the free nodes, and determines the initial condisitons for the timestep. Args: Solid (object): A solid object created from the solid class. Body (object): A body object created from the swimmer class. PyFEA (object): A FEA solver object created from the PyFEA class. t (float): Current simulation time. TSTEP (flaot): Small, incremental distance/time offsets. outerCorr (int): Current FSI subiteration number. SW_VISC_DRAG (bool): Used to determine if viscous forces should be included. delFs (float): NumPy array of viscous force components. SW_INTERP_MTD (bool): Used to determine if linear or cubic spline interpolation between fluid and solid domains should be used. C (float): Body chord length. i_t (int): Current time-step number. """ # Superposing the structural displacements if (outerCorr > 1): Solid.nodes[:,0] += (self.nodeDispl[:,0] - self.nodeDisplOld[:,0]) Solid.nodes[:,1] += (self.nodeDispl[:,1] - self.nodeDisplOld[:,1]) if (outerCorr <= 1): # Updating the new kinematics Solid.nodes[:,0] = (Solid.nodesNew[:,0] - Solid.nodesNew[0,0])*np.cos(THETA) Solid.nodes[:,1] = HEAVE + (Solid.nodesNew[:,0] - Solid.nodesNew[0,0])*np.sin(THETA) # Calculating the shift in node positions with the swimming velocity nodeDelxp = Body.AF.x_le * np.ones((Solid.Nelements + 1,1)) # nodeDelzp = Body.AF.z_le * np.ones((Solid.Nelements + 1,1)) #Superposiing the kinematics and swimming translations Solid.nodes[:,0] = Solid.nodes[:,0] + nodeDelxp.T # Solid.nodes[:,1] = Solid.nodes[:,1] + nodeDelzp.T # Determine the load conditons from the fluid solver # Calculate the panel lengths and normal vectors (nx,nz,lp) = panel_vectors(Body.AF.x,Body.AF.z)[2:5] # Calculate the force magnitude acting on the panel due to pressure, # then calculate the x-z components of this force magPF = Body.p * lp * 1. pF = np.zeros((Body.N,2)) if (SW_VISC_DRAG == 1): pF[:,0] = (magPF.T * nx.T * -1.) + delFs[:,0] pF[:,1] = (magPF.T * nz.T * -1.) + delFs[:,1] else: pF[:,0] = magPF.T * nx.T * -1. pF[:,1] = magPF.T * nz.T * -1. # Determine the moment arm between top and bottom panel points, and # collapse force and moments to the camber line colM = np.zeros((0.5*Body.N,1)) colPF = np.zeros((0.5*Body.N,2)) meanPt = np.zeros((0.5*Body.N,2)) for i in xrange(int(0.5*Body.N)): meanPt[i,0] = 0.5*(Body.AF.x_mid[0,i] + Body.AF.x_mid[0,-(i+1)]) meanPt[i,1] = 0.5*(Body.AF.z_mid[0,i] + Body.AF.z_mid[0,-(i+1)]) colPF[i,:] = pF[i,:] + pF[-(i+1),:] colM[i,:] = -1. * pF[i,0] * (Body.AF.z_mid[0,i] - meanPt[i,1]) + \ pF[i,1] * (Body.AF.x_mid[0,i] - meanPt[i,0]) + \ -1. * pF[-(i+1),0] * (Body.AF.z_mid[0,-(i+1)] - meanPt[i,1]) + \ pF[-(i+1),1] * (Body.AF.x_mid[0,-(i+1)] - meanPt[i,0]) colPF = np.flipud(colPF) colM = np.flipud(colM) # Interpolate the collapsed forces and moments onto the structural mesh nodalInput = np.zeros((Solid.Nnodes,6)) if (SW_INTERP_MTD == True): f1 = extrap1d(interp1d(Solid.meanline_c0[0.5*Body.N:], colPF[:,0])) f2 = extrap1d(interp1d( Solid.meanline_c0[0.5*Body.N:], colPF[:,1])) f3 = extrap1d(interp1d(Solid.meanline_c0[0.5*Body.N:], colM[:,0])) nodalInput[:,0] = f1(Solid.nodes[:,2]) nodalInput[:,1] = f2(Solid.nodes[:,2]) nodalInput[:,5] = f3(Solid.nodes[:,2]) # nodalInput[:,0] = np.interp(Solid.nodes[:,2], Solid.meanline_c0[0.5*Body.N:], colPF[:,0], left=0, right=0) # nodalInput[:,1] = np.interp(Solid.nodes[:,2], Solid.meanline_c0[0.5*Body.N:], colPF[:,1], left=0, right=0) # nodalInput[:,5] = np.interp(Solid.nodes[:,2], Solid.meanline_c0[0.5*Body.N:], colM[:,0], left=0, right=0) else: nodalInput[:,0] = spline(Solid.meanline_c0[0.5*Body.N:], colPF[:,0], Solid.nodes[:,2]) nodalInput[:,1] = spline(Solid.meanline_c0[0.5*Body.N:], colPF[:,1], Solid.nodes[:,2]) nodalInput[:,5] = spline(Solid.meanline_c0[0.5*Body.N:], colM[:,0], Solid.nodes[:,2]) # Rotate force components into the relative cooridnate system (nodalInput[:,0], nodalInput[:,1]) = self.rotatePts(nodalInput[:,0], nodalInput[:,1], -THETA) # Create the load matrix Fload = np.zeros((3*(Solid.Nnodes),1)) Fload[0::3,0] = np.copy(nodalInput[:,0]) Fload[1::3,0] = np.copy(nodalInput[:,1]) Fload[2::3,0] = np.copy(nodalInput[:,5]) # Create element area matrix A = np.copy(Solid.tBeamStruct[:,0]) # Create area moment of inertia matrix I = 1. * Solid.tBeamStruct[:,0]**3 / 12 # Initial element length l_0 = C / Solid.Nelements # Initial displacements and velocities # if (i_t <= 1 and outerCorr <= 1): temp = 3 * Solid.fixedCounter if (i_t <= 1 and outerCorr <= 1): PyFEA.U_n = np.zeros((3*Solid.Nnodes,1)) PyFEA.Udot_n = np.zeros((3*Solid.Nnodes,1)) PyFEA.UdotDot_n = np.zeros((3*Solid.Nnodes - temp,1)) PyFEA.U_nPlus = np.zeros((3*Solid.Nnodes - temp,1)) PyFEA.Udot_nPlus = np.zeros((3*Solid.Nnodes - temp,1)) PyFEA.UdotDot_nPlus = np.zeros((3*Solid.Nnodes - temp,1)) elif (i_t > 0 and outerCorr <= 1): PyFEA.U_n = np.zeros((3*Solid.Nnodes,1)) PyFEA.Udot_n = np.zeros((3*Solid.Nnodes,1)) PyFEA.UdotDot_n = np.zeros((3*Solid.Nnodes - temp,1)) PyFEA.U_n[temp:,0] = PyFEA.U_nPlus.T PyFEA.Udot_n[temp:,0] = PyFEA.Udot_nPlus.T PyFEA.UdotDot_n = PyFEA.Udot_nPlus PyFEA.Fload = np.copy(Fload) PyFEA.A = np.copy(A) PyFEA.I = np.copy(I) PyFEA.l = l_0 * np.ones(Solid.Nelements)
def setInterfaceForce(self, Solid, Body, PyFEA, t, TSTEP, outerCorr, SWITCH_VISC_DRAG, delFs, SWITCH_INTERP_MTD, C, i_t): """ Updates the structural mesh position, calculates the traction forces on the free nodes, and determines the initial condisitons for the timestep. Returns the node positions, traction forces, and initial conditions for the structural solver. Keyword arguments: """ # Determine current pitching angle and heave position # TODO: Add heaving functionality to kinematics theta = Body.MP.THETA_MAX * np.sin(2 * np.pi * Body.MP.F * (t + TSTEP) + Body.MP.PHI) heave = 0 # Superposing the structural displacements if (outerCorr > 1): Solid.nodes[:,0] += (self.nodeDispl[:,0] - self.nodeDisplOld[:,0]) Solid.nodes[:,1] += (self.nodeDispl[:,1] - self.nodeDisplOld[:,1]) if (outerCorr <= 1): # Updating the new kinematics Solid.nodes[:,0] = (Solid.nodesNew[:,0] - Solid.nodesNew[0,0])*np.cos(theta) Solid.nodes[:,1] = heave + (Solid.nodesNew[:,0] - Solid.nodesNew[0,0])*np.sin(theta) # Calculating the shift in node positions with the swimming velocity nodeDelxp = Body.AF.x_le * np.ones((Solid.Nelements + 1,1)) nodeDelzp = Body.AF.z_le * np.ones((Solid.Nelements + 1,1)) #Superposiing the kinematics and swimming translations Solid.nodes[:,0] = Solid.nodes[:,0] + nodeDelxp.T Solid.nodes[:,1] = Solid.nodes[:,1] + nodeDelzp.T # Determine the load conditons from the fluid solver # Calculate the panel lengths and normal vectors (nx,nz,lp) = panel_vectors(Body.AF.x,Body.AF.z)[2:5] # Calculate the force magnitude acting on the panel due to pressure, # then calculate the x-z components of this force magPF = Body.p * lp * 1. pF = np.zeros((Body.N,2)) if (SWITCH_VISC_DRAG == 1): pF[:,0] = (magPF.T * nx.T * -1.) + delFs[:,0] pF[:,1] = (magPF.T * nz.T * -1.) + delFs[:,1] else: pF[:,0] = magPF.T * nx.T * -1. pF[:,1] = magPF.T * nz.T * -1. # Determine the moment arm between top and bottom panel points, and # collapse force and moments to the camber line colM = np.zeros((0.5*Body.N,1)) colPF = np.zeros((0.5*Body.N,2)) meanPt = np.zeros((0.5*Body.N,2)) for i in xrange(int(0.5*Body.N)): meanPt[i,0] = 0.5*(Body.AF.x_mid[0,i] + Body.AF.x_mid[0,-(i+1)]) meanPt[i,1] = 0.5*(Body.AF.z_mid[0,i] + Body.AF.z_mid[0,-(i+1)]) colPF[i,:] = pF[i,:] + pF[-(i+1),:] colM[i,:] = -1. * pF[i,0] * (Body.AF.z_mid[0,i] - meanPt[i,1]) + \ pF[i,1] * (Body.AF.x_mid[0,i] - meanPt[i,0]) + \ -1. * pF[-(i+1),0] * (Body.AF.z_mid[0,-(i+1)] - meanPt[i,1]) + \ pF[-(i+1),1] * (Body.AF.x_mid[0,-(i+1)] - meanPt[i,0]) colPF = np.flipud(colPF) colM = np.flipud(colM) relXp = Body.AF.x - np.min(Body.AF.x) relXc = Body.AF.x_mid - np.min(Body.AF.x) # Interpolate the collapsed forces and moments onto the structural mesh nodalInput = np.zeros((Solid.Nnodes,6)) if (SWITCH_INTERP_MTD == 1): nodalInput[:,0] = np.interp(Solid.nodes[:,2], Solid.meanline_c0[0.5*Body.N:], colPF[:,0], left=0, right=0) nodalInput[:,1] = np.interp(Solid.nodes[:,2], Solid.meanline_c0[0.5*Body.N:], colPF[:,1], left=0, right=0) nodalInput[:,5] = np.interp(Solid.nodes[:,2], Solid.meanline_c0[0.5*Body.N:], colM[:,0], left=0, right=0) else: nodalInput[:,0] = spline(Solid.meanline_c0[0.5*Body.N:], colPF[:,0], Solid.nodes[:,2]) nodalInput[:,1] = spline(Solid.meanline_c0[0.5*Body.N:], colPF[:,1], Solid.nodes[:,2]) nodalInput[:,5] = spline(Solid.meanline_c0[0.5*Body.N:], colM[:,0], Solid.nodes[:,2]) # Rotate force components into the relative cooridnate system (nodalInput[:,0], nodalInput[:,1]) = self.rotatePts(nodalInput[:,0], nodalInput[:,1], -theta) # Create the load matrix Fload = np.zeros((3*(Solid.Nnodes),1)) Fload[0::3,0] = np.copy(nodalInput[:,0]) Fload[1::3,0] = np.copy(nodalInput[:,1]) Fload[2::3,0] = np.copy(nodalInput[:,5]) # Create element area matrix A = np.copy(Solid.tBeamStruct[:,0]) # Create area moment of inertia matrix I = 1. * Solid.tBeamStruct[:,0]**3 / 12 # Set fixed nodes fixedNodes = np.copy(Solid.fixedCounter) # Initial element length l_0 = C / Solid.Nelements # Initial displacements and velocities # if (i_t <= 1 and outerCorr <= 1): if (i_t <= 1): PyFEA.U_n = np.zeros((3*(Solid.Nnodes),1)) PyFEA.Udot_n = np.zeros((3*(Solid.Nnodes),1)) PyFEA.UdotDot_n = np.zeros((3*(Solid.Nelements+1),1)) PyFEA.U_nPlus = np.zeros((3*(Solid.Nelements+1),1)) PyFEA.Udot_nPlus = np.zeros((3*(Solid.Nelements+1),1)) PyFEA.UdotDot_nPlus = np.zeros((3*(Solid.Nelements+1),1)) if (i_t > 1 and outerCorr == 1): PyFEA.initU = np.copy(PyFEA.U_nPlus) PyFEA.initUdot = np.copy(PyFEA.Udot_nPlus) if (i_t > 1): PyFEA.U_n.resize((3*(Solid.Nnodes),1)) PyFEA.Udot_n.resize((3*(Solid.Nnodes),1)) # PyFEA.U_n = np.zeros() # PyFEA.Udot_n = np.zeros((3*(Solid.Nnodes),1)) PyFEA.U_n[3*fixedNodes:,0] = PyFEA.initU.T PyFEA.Udot_n[3*fixedNodes:,0] = PyFEA.initUdot.T # Resize matricies to acount for all nodes after first subiteration PyFEA.Fload.resize((3*Solid.Nnodes,1)) PyFEA.Fload = np.copy(Fload) PyFEA.A = np.copy(A) PyFEA.I = np.copy(I) PyFEA.l = l_0 * np.ones(Solid.Nelements) return relXp, relXc
def body_plot(Swimmers, SW_PLOT_FIG): global n_fig if SW_PLOT_FIG: figure = plt.figure(1) ax = figure.gca(projection='3d') # ax.set_aspect('equal') This feature has not been implemented in 3D plotting yet figure.set_size_inches(16, 9) plt.tick_params(labelsize=28) for Swim in Swimmers: (nx, ny, nz, txs, tys, tzs, txc, tyc, tzc, lps, lpc) = panel_vectors(Swim.Body.AF.x, Swim.Body.AF.y, Swim.Body.AF.z) x = Swim.Body.AF.x_mid[::5, ::5, 0] + 0.1 * nx[::5, ::5] y = Swim.Body.AF.y_mid[::5, ::5, 0] + 0.1 * ny[::5, ::5] z = Swim.Body.AF.z_mid[::5, ::5, 0] + 0.1 * nz[::5, ::5] nx = nx[::5, ::5] ny = ny[::5, ::5] nz = nz[::5, ::5] ax.quiver(x, y, z, nx, ny, nz, length=0.1, color='r') ax.quiver(Swim.Body.AF.x_mid[::5, ::5, 0], Swim.Body.AF.y_mid[::5, ::5, 0], Swim.Body.AF.z_mid[::5, ::5, 0], txs[::5, ::5], tys[::5, ::5], tzs[::5, ::5], length=0.1, color='g') ax.quiver(Swim.Body.AF.x_mid[::5, ::5, 0], Swim.Body.AF.y_mid[::5, ::5, 0], Swim.Body.AF.z_mid[::5, ::5, 0], txc[::5, ::5], tyc[::5, ::5], tzc[::5, ::5], length=0.1, color='b') ax.plot_surface(Swim.Body.AF.x, Swim.Body.AF.y, Swim.Body.AF.z, rstride=1, cstride=1, linewidth=0, color='k', antialiased=True) # Determine if the output directory exists. If not, create the directory. if not os.path.exists('./movies'): os.makedirs('./movies') ax.set_xlim( np.min(Swim.Body.AF.x) - 1.25, np.min(Swim.Body.AF.x) + 1.25) ax.set_ylim(-1.25, 1.25) ax.set_zlim(-1.25, 1.25) ax.view_init(elev=18, azim=-124) ax.set_xlabel('$X$ $[m]$', fontsize=28) ax.set_ylabel('$Y$ $[m]$', fontsize=28) ax.set_zlabel('$Z$ $[m]$', fontsize=28) ax.xaxis._axinfo['label']['space_factor'] = 2.0 ax.yaxis._axinfo['label']['space_factor'] = 2.0 ax.zaxis._axinfo['label']['space_factor'] = 2.0 figure.savefig('./movies/%05i.png' % (n_fig), format='png') plt.clf() n_fig += 1
def surface_kinematics(self, DSTEP1, DSTEP2, DSTEP3, TSTEP, THETA_MINUS, THETA_PLUS, HEAVE_MINUS, HEAVE_PLUS, DEL_T, T, i): """Calculates the body-frame surface velocities of body panels. Also finds the body panel source strengths based on these surface velocities. Args: DSTEP, TSTEP: Incremental distance/time passed into neutral_axis(). DEL_T: Time step length. T: Time of current step. i: Time step number. THETA_MINUS: Pitching angle minus a small time difference (TSTEP) THETA_PLUS: Pitching angle plus a small time difference (TSTEP) """ Nc = self.BF.x_mid.shape[0] Ns = self.BF.x_mid.shape[1] if i == 0: x_col = self.BF.x_mid y_col = self.BF.y_mid z_col = self.BF.z_mid # Panel midpoint velocity calculations # Calculating the surface positions at tplus(tp) and tminus(tm) (xtpneut, ytpneut, ztpneut) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, 0, 0, 0) (xtpdp_y, ytpdp_y, ztpdp_y) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, 0, DSTEP2, DSTEP3) (xtpdp_x, ytpdp_x, ztpdp_x) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, DSTEP1, 0, 0) (xtpdm_y, ytpdm_y, ztpdm_y) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, 0, -DSTEP2, -DSTEP3) (xtpdm_x, ytpdm_x, ztpdm_x) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, -DSTEP1, 0, 0) (xtmneut, ytmneut, ztmneut) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, 0, 0, 0) (xtmdp_y, ytmdp_y, ztmdp_y) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, 0, DSTEP2, DSTEP3) (xtmdp_x, ytmdp_x, ztmdp_x) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, DSTEP1, 0, 0) (xtmdm_y, ytmdm_y, ztmdm_y) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, 0, -DSTEP2, -DSTEP3) (xtmdm_x, ytmdm_x, ztmdm_x) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, -DSTEP1, 0, 0) # Displaced airfoil's panel midpoints for times tplus(tp) and tminus(tm) xctp = xtpneut + point_vectors( Nc, Ns, (xtpdp_y, ytpdp_y, ztpdp_y), (xtpdm_y, ytpdm_y, ztpdm_y), (xtpdp_x, ytpdp_x, ztpdp_x), (xtpdm_x, ytpdm_x, ztpdm_x))[0] * z_col xctm = xtmneut + point_vectors( Nc, Ns, (xtmdp_y, ytmdp_y, ztmdp_y), (xtmdm_y, ytmdm_y, ztmdm_y), (xtmdp_x, ytmdp_x, ztmdp_x), (xtmdm_x, ytmdm_x, ztmdm_x))[0] * z_col yctp = ytpneut + point_vectors( Nc, Ns, (xtpdp_y, ytpdp_y, ztpdp_y), (xtpdm_y, ytpdm_y, ztpdm_y), (xtpdp_x, ytpdp_x, ztpdp_x), (xtpdm_x, ytpdm_x, ztpdm_x))[1] * z_col yctm = ytmneut + point_vectors( Nc, Ns, (xtmdp_y, ytmdp_y, ztmdp_y), (xtmdm_y, ytmdm_y, ztmdm_y), (xtmdp_x, ytmdp_x, ztmdp_x), (xtmdm_x, ytmdm_x, ztmdm_x))[1] * z_col zctp = ztpneut + point_vectors( Nc, Ns, (xtpdp_y, ytpdp_y, ztpdp_y), (xtpdm_y, ytpdm_y, ztpdm_y), (xtpdp_x, ytpdp_x, ztpdp_x), (xtpdm_x, ytpdm_x, ztpdm_x))[2] * z_col zctm = ztmneut + point_vectors( Nc, Ns, (xtmdp_y, ytmdp_y, ztmdp_y), (xtmdm_y, ytmdm_y, ztmdm_y), (xtmdp_x, ytmdp_x, ztmdp_x), (xtmdm_x, ytmdm_x, ztmdm_x))[2] * z_col # Velocity calculations on the surface panel midpoints self.vx = (xctp - xctm) / (2 * TSTEP) self.vy = (yctp - yctm) / (2 * TSTEP) self.vz = (zctp - zctm) / (2 * TSTEP) elif i == 1: # First-order backwards differencing of body collocation point positions self.vx = (self.AF.x_mid[:, :, 0] - self.AF.x_mid[:, :, 1]) / DEL_T - self.V0 self.vy = (self.AF.y_mid[:, :, 0] - self.AF.y_mid[:, :, 1]) / DEL_T self.vz = (self.AF.z_mid[:, :, 0] - self.AF.z_mid[:, :, 1]) / DEL_T else: # Second-order backwards differencing of body collocation point positions self.vx = (3 * self.AF.x_mid[:, :, 0] - 4 * self.AF.x_mid[:, :, 1] + self.AF.x_mid[:, :, 2]) / (2 * DEL_T) - self.V0 self.vy = (3 * self.AF.y_mid[:, :, 0] - 4 * self.AF.y_mid[:, :, 1] + self.AF.y_mid[:, :, 2]) / (2 * DEL_T) self.vz = (3 * self.AF.z_mid[:, :, 0] - 4 * self.AF.z_mid[:, :, 1] + self.AF.z_mid[:, :, 2]) / (2 * DEL_T) # # Body source strengths with normal vector pointing outward (overall sigma pointing outward) (nx, ny, nz) = panel_vectors(self.AF.x, self.AF.y, self.AF.z)[0:3] self.sigma = nx * (self.V0 + self.vx) + ny * self.vy + nz * self.vz
def panel_positions(self, DSTEP1, DSTEP2, DSTEP3, T, THETA, HEAVE): """Updates all the absolute-frame coordinates of the body. Args: DSTEP: Small incremental distance to pass into neutral_plane(). T: Time of current step. THETA: Current pitching angle. """ # Nc = int(0.5*(self.BF.x.shape[0]-1)) Nc = self.BF.x.shape[0] Ns = self.BF.x.shape[1] bfx = self.BF.x bfy = self.BF.y #No y information coming from the geometry class yet bfz = self.BF.z bfz_col = self.BF.z_mid V0 = self.V0 # Used only for x_le (x_neut, y_neut, z_neut) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE) # Infinitesimal differences on the neutral axis to calculate the tangential and normal vectors v1 = (xdp_y, ydp_y, zdp_y) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE, 0, DSTEP2, DSTEP3) v2 = (xdm_y, ydm_y, zdm_y) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE, 0, -DSTEP2, -DSTEP3) v3 = (xdp_x, ydp_x, zdp_x) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE, DSTEP1, 0, 0) v4 = (xdm_x, ydm_x, zdm_x) = self.neutral_plane(bfx, bfy, T, THETA, HEAVE, -DSTEP1, 0, 0) # Absolute-frame panel endpoint positions for time t afx = x_neut + point_vectors(Nc, Ns, v1, v2, v3, v4)[0] * bfz afy = y_neut + point_vectors(Nc, Ns, v1, v2, v3, v4)[1] * bfz afz = z_neut + point_vectors(Nc, Ns, v1, v2, v3, v4)[2] * bfz # Absolute-frame panel midpoint positions x_mid = (afx[1:, :-1] + afx[:-1, :-1]) / 2 y_mid = (afy[1:, :-1] + afy[:-1, :-1]) / 2 z_mid = (afz[1:, :-1] + afz[:-1, :-1]) / 2 # Collocation points are the points where impermeable boundary condition is forced # They should be shifted inside or outside of the boundary depending on the dirichlet or neumann condition # Shifting surface collocation points some percent of the height from the neutral axis # Normal vectors point outward but positive S is inward, so the shift must be subtracted from the panel midpoints afx_col = x_mid - self.S * panel_vectors(bfx, bfy, bfz)[0] * np.absolute(bfz_col) afy_col = y_mid - self.S * panel_vectors(bfy, bfy, bfz)[1] * np.absolute(bfz_col) afz_col = z_mid - self.S * panel_vectors(bfz, bfy, bfz)[2] * np.absolute(bfz_col) self.AF.x = afx self.AF.y = afy self.AF.z = afz self.AF.x_col = afx_col self.AF.y_col = afy_col self.AF.z_col = afz_col self.AF.x_mid[:, :, 0] = x_mid self.AF.y_mid[:, :, 0] = y_mid self.AF.z_mid[:, :, 0] = z_mid self.AF.x_neut = x_neut self.AF.y_neut = y_neut self.AF.z_neut = z_neut # Location of leading edge (currently pitching motion only) self.AF.x_le = V0 * T self.AF.y_le = 0 self.AF.z_le = HEAVE
def wake_rollup(Swimmers, DEL_T, i, P): """Performs wake rollup on the swimmers' wake panels. Args: Swimmers: List of Swimmer objects being simulated. DEL_T: Time step length. i: Time step number. """ if (P['SW_ROLLUP']): # Wake panels initialize when i==1 if i == 0: pass else: NT = i # Number of targets (wake panel points that are rolling up) for SwimT in Swimmers: SwimT.Wake.vx = np.zeros(NT) SwimT.Wake.vz = np.zeros(NT) wake_x_midT = 0.5 * (SwimT.Wake.x[1:i + 1] + SwimT.Wake.x[:i]) wake_z_midT = 0.5 * (SwimT.Wake.z[1:i + 1] + SwimT.Wake.z[:i]) target = np.vstack((wake_x_midT, wake_z_midT)) for SwimI in Swimmers: wake_x_midI = 0.5 * (SwimI.Wake.x[1:i + 1] + SwimI.Wake.x[:i]) wake_z_midI = 0.5 * (SwimI.Wake.z[1:i + 1] + SwimI.Wake.z[:i]) edge_x_mid = 0.5 * (SwimI.Edge.x[1] + SwimI.Edge.x[0]) edge_z_mid = 0.5 * (SwimI.Edge.z[1] + SwimI.Edge.z[0]) ed_places = np.vstack((edge_x_mid, edge_z_mid)) bd_places = np.vstack( (SwimT.Body.AF.x_col, SwimT.Body.AF.z_col)) bs_places = np.vstack( (SwimT.Body.AF.x_mid[0, :], SwimT.Body.AF.z_mid[0, :])) wd_places = np.vstack((wake_x_midI, wake_z_midI)) (nx_b, nz_b, lbpanel) = panel_vectors(SwimI.Body.AF.x, SwimI.Body.AF.z)[2:] (nx_e, nz_e, lepanel) = panel_vectors(SwimI.Edge.x, SwimI.Edge.z)[2:] (nx_w, nz_w, lwpanel) = panel_vectors(SwimI.Wake.x[:i + 1], SwimI.Wake.z[:i + 1])[2:] n_b = np.vstack((nx_b, nz_b)) n_e = np.vstack((nx_e, nz_e)) n_w = np.vstack((nx_w, nz_w)) # Body source influence on wake velocity u_bs = np.real( fmm_part( "G", iprec=5, kernel=0, sources=bs_places.T, target=target.T, mop_charge=SwimI.Body.sigma * lbpanel)) / np.pi SwimT.Wake.vx += 2. * u_bs[:, 0] SwimT.Wake.vz += -2. * u_bs[:, 1] # Body doublet influence on wake velocity u_bd = np.real( fmm_part("G", iprec=5, kernel=0, sources=bd_places.T, target=target.T, dipvec=n_b.T, dip_charge=SwimI.Body.mu * lbpanel)) / np.pi SwimT.Wake.vx += 2. * u_bd[:, 0] SwimT.Wake.vz += -2. * u_bd[:, 1] # TE panel influence on wake velocity u_te = np.real( fmm_part("G", iprec=5, kernel=0, sources=ed_places.T, target=target.T, dipvec=n_e.T, dip_charge=SwimI.Edge.mu * lepanel)) / np.pi SwimT.Wake.vx += 2. * u_te[:, 0] SwimT.Wake.vz += -2. * u_te[:, 1] # Wake double influence on wake velocity u_w = np.real( fmm_part("G", iprec=5, kernel=0, sources=wd_places.T, target=target.T, dipvec=n_w.T, dip_charge=SwimI.Wake.mu[1:i + 1] * lwpanel)) / np.pi u_w = np.nan_to_num(u_w) SwimT.Wake.vx += 2. * u_w[:, 0] SwimT.Wake.vz += -2. * u_w[:, 1] for Swim in Swimmers: # Modify wake with the total induced velocity Swim.Wake.x[1:i + 1] += Swim.Wake.vx * DEL_T Swim.Wake.z[1:i + 1] += Swim.Wake.vz * DEL_T
def solve_phi(Swimmers, P, i, outerCorr=0): """Solves the boundary integral equation using a Kutta condition and the Fast Multipole Method. Args: Swimmers: List of Swimmer objects being simulated. RHO: Fluid density. DEL_T: Time step length. i: Time step number. """ for Swim in Swimmers: if (outerCorr <= 1): # mu_past used in differencing for pressure Swim.Body.mu_past[1:4, :] = Swim.Body.mu_past[0:3, :] Swim.Body.mu_past[0, :] = Swim.Body.mu (sigma_all, mu_w_all, a_b, a_e, b_e) = influence_matrices(Swimmers, i) for SwimI in Swimmers: a_e[:, SwimI.i_b] = -b_e[:, SwimI.i_e] a_e[:, SwimI.i_b + SwimI.Body.N - 1] = b_e[:, SwimI.i_e] a = a_b + a_e # Prepare FMM inputs b_places = np.vstack( (Swimmers[0].Body.AF.x_mid[0, :], Swimmers[0].Body.AF.z_mid[0, :])) target = np.vstack((Swimmers[0].Body.AF.x_col, Swimmers[0].Body.AF.z_col)) lpanel = panel_vectors(Swimmers[0].Body.AF.x, Swimmers[0].Body.AF.z)[-1] if (i > 0): w_places = np.vstack( (0.5 * (Swimmers[0].Wake.x[1:i + 1] + Swimmers[0].Wake.x[:i]), 0.5 * (Swimmers[0].Wake.z[1:i + 1] + Swimmers[0].Wake.z[:i]))) (nx, nz, lwpanel) = panel_vectors(Swimmers[0].Wake.x[:i + 1], Swimmers[0].Wake.z[:i + 1])[2:] n_w = np.vstack((nx, nz)) # Get right-hand side b_body = np.real( fmm_part("P", iprec=5, kernel=0, sources=b_places.T, target=target.T, mop_charge=sigma_all * lpanel)) / 2. / np.pi if i == 0: b = -b_body else: b_wake = np.real( fmm_part("P", iprec=5, kernel=0, sources=w_places.T, target=target.T, dipvec=n_w.T, dip_charge=mu_w_all * lwpanel)) / 2. / np.pi b = -b_body - b_wake # Solve for bodies' doublet strengths using explicit Kutta mu_b_all = np.linalg.solve(a, b) for Swim in Swimmers: Swim.Body.pressure(P, i) Swim.mu_guess = np.empty(2) # [0] is current guess, [1] is previous Swim.delta_p = np.empty(2) # [0] is current delta_p, [1] is previous Swim.Body.mu[:] = mu_b_all[Swim.i_b:Swim.i_b + Swim.Body.N] Swim.mu_guess[0] = Swim.Body.mu[-1] - Swim.Body.mu[0] Swim.Edge.mu = Swim.mu_guess[0] Swim.Edge.gamma[0] = -Swim.Edge.mu Swim.Edge.gamma[1] = Swim.Edge.mu # Get gamma of body panels for use in wake rollup Swim.Body.gamma[0] = -Swim.Body.mu[0] Swim.Body.gamma[1:-1] = Swim.Body.mu[:-1] - Swim.Body.mu[1:] Swim.Body.gamma[-1] = Swim.Body.mu[-1]
def setSpringForce(self, Body, Solid, PyFEA, P, outerCorr, delFs, i_t): # Superposing the structural displacements if (outerCorr > 1): Solid.nodes[:,0] += (self.nodeDispl[:,0] - self.nodeDisplOld[:,0]) Solid.nodes[:,1] += (self.nodeDispl[:,1] - self.nodeDisplOld[:,1]) if (outerCorr <= 1): # Updating the new kinematics Solid.nodes[:,0] = (Solid.nodesNew[:,0] - Solid.nodesNew[0,0])*np.cos(P['THETA'][i_t]) Solid.nodes[:,1] = P['HEAVE'][i_t] + (Solid.nodesNew[:,0] - Solid.nodesNew[0,0])*np.sin(P['THETA'][i_t]) # Calculating the shift in node positions with the swimming velocity nodeDelxp = Body.AF.x_le * np.ones((Solid.Nelements + 1,1)) # nodeDelzp = Body.AF.z_le * np.ones((Solid.Nelements + 1,1)) #Superposiing the kinematics and swimming translations Solid.nodes[:,0] = Solid.nodes[:,0] + nodeDelxp.T # Solid.nodes[:,1] = Solid.nodes[:,1] + nodeDelzp.T # Determine the load conditons from the fluid solver # Calculate the panel lengths and normal vectors (nx,nz,lp) = panel_vectors(Body.AF.x,Body.AF.z)[2:5] # Calculate the force magnitude acting on the panel due to pressure, # then calculate the x-z components of this force magPF = Body.p * lp * 1. pF = np.zeros((Body.N,3)) if P['SW_VISC_DRAG']: pF[:,0] = (magPF.T * nx.T * -1.) + delFs[:,0] pF[:,2] = (magPF.T * nz.T * -1.) + delFs[:,1] else: pF[:,0] = magPF.T * nx.T * -1. pF[:,2] = magPF.T * nz.T * -1. # Calculating the moment about the leading edge r = np.zeros((Body.N, 3)) r[:,0] = Body.AF.x_mid[0,:].T - Body.AF.x_le r[:,2] = Body.AF.z_mid[0,:].T - Body.AF.z_le delM_le = np.cross(r, pF) Nf = -np.sum(delM_le[:,1]) # Calculating the inertial torque resulting from the vertical # acceleration of the leading edge Ni = -P['RHO_S'] * Solid.tmax * P['C']**2 * np.pi**2 * P['F']**2 * np.cos(PyFEA.theta_n) * P['INERTIA'][i_t] # Calculating the moment of intertia about the leading edge. An extra # 0.5 is multiplied in to approximate the teardrop geometry's mass. I = 0.5 * PyFEA.RHO_S * Solid.tmax * P['C']**3 / 3. # Define spring and dampening constants kappa_1 = P['KAPPA_1'] kappa_2 = P['KAPPA_2'] zeta = P['ZETA'] if (i_t <= 1 and outerCorr <= 1): PyFEA.theta_n = 0. PyFEA.thetaDot_n = 0. PyFEA.thetaDotDot_n = 0. PyFEA.theta_nPlus = 0. PyFEA.thetaDot_nPlus = 0. PyFEA.thetaDotDot_nPlus = 0. elif (i_t > 0 and outerCorr <= 1): PyFEA.theta_n = 0. PyFEA.thetaDot_n = 0. PyFEA.thetaDotDot_n = 0. PyFEA.theta_n = np.copy(PyFEA.theta_nPlus) PyFEA.thetaDot_n = np.copy(PyFEA.thetaDot_nPlus) PyFEA.thetaDotDot_n = np.copy(PyFEA.thetaDotDot_nPlus) PyFEA.I = np.copy(I) PyFEA.kappa_1 = np.copy(kappa_1) PyFEA.kappa_2 = np.copy(kappa_2) PyFEA.zeta = np.copy(zeta) PyFEA.Nf = np.copy(Nf) PyFEA.Ni = np.copy(Ni)
def surface_kinematics(self, DSTEP, TSTEP, THETA_MINUS, THETA_PLUS, HEAVE_MINUS, HEAVE_PLUS, DEL_T, T, i): """Calculates the body-frame surface velocities of body panels. Also finds the body panel source strengths based on these surface velocities. Args: DSTEP, TSTEP: Incremental distance/time passed into neutral_axis(). DEL_T: Time step length. T: Time of current step. i: Time step number. THETA_MINUS: Pitching angle minus a small time difference (TSTEP) THETA_PLUS: Pitching angle plus a small time difference (TSTEP) """ if i == 0: x_col = self.BF.x_col z_col = self.BF.z_col # Panel midpoint velocity calculations # Calculating the surface positions at tplus(tp) and tminus(tm) (xtpneut, ztpneut) = self.neutral_axis(x_col, T, THETA_PLUS, HEAVE_PLUS, 0) (xtpdp, ztpdp) = self.neutral_axis(x_col, T, THETA_PLUS, HEAVE_PLUS, DSTEP) (xtpdm, ztpdm) = self.neutral_axis(x_col, T, THETA_PLUS, HEAVE_PLUS, -DSTEP) (xtmneut, ztmneut) = self.neutral_axis(x_col, T, THETA_MINUS, HEAVE_MINUS, 0) (xtmdp, ztmdp) = self.neutral_axis(x_col, T, THETA_MINUS, HEAVE_MINUS, DSTEP) (xtmdm, ztmdm) = self.neutral_axis(x_col, T, THETA_MINUS, HEAVE_MINUS, -DSTEP) # Displaced airfoil's panel midpoints for times tplus(tp) and tminus(tm) xctp = xtpneut + point_vectors(xtpdp, xtpdm, ztpdp, ztpdm)[2] * z_col xctm = xtmneut + point_vectors(xtmdp, xtmdm, ztmdp, ztmdm)[2] * z_col zctp = ztpneut + point_vectors(xtpdp, xtpdm, ztpdp, ztpdm)[3] * z_col zctm = ztmneut + point_vectors(xtmdp, xtmdm, ztmdp, ztmdm)[3] * z_col # Velocity calculations on the surface panel midpoints self.vx = (xctp - xctm) / (2 * TSTEP) self.vz = (zctp - zctm) / (2 * TSTEP) elif i == 1: # First-order backwards differencing of body collocation point positions self.vx = (self.AF.x_mid[0, :] - self.AF.x_mid[1, :]) / DEL_T - self.V0 self.vz = (self.AF.z_mid[0, :] - self.AF.z_mid[1, :]) / DEL_T else: # Second-order backwards differencing of body collocation point positions self.vx = (3 * self.AF.x_mid[0, :] - 4 * self.AF.x_mid[1, :] + self.AF.x_mid[2, :]) / (2 * DEL_T) - self.V0 self.vz = (3 * self.AF.z_mid[0, :] - 4 * self.AF.z_mid[1, :] + self.AF.z_mid[2, :]) / (2 * DEL_T) # Body source strengths with normal vector pointing outward (overall sigma pointing outward) (nx, nz) = panel_vectors(self.AF.x, self.AF.z)[2:4] self.sigma = nx * (self.V0 + self.vx) + nz * self.vz
def surface_kinematics(self, DSTEP1, DSTEP2, DSTEP3, TSTEP, THETA_MINUS, THETA_PLUS, HEAVE_MINUS, HEAVE_PLUS, DEL_T, T, i): """Calculates the body-frame surface velocities of body panels. Also finds the body panel source strengths based on these surface velocities. Args: DSTEP, TSTEP: Incremental distance/time passed into neutral_axis(). DEL_T: Time step length. T: Time of current step. i: Time step number. THETA_MINUS: Pitching angle minus a small time difference (TSTEP) THETA_PLUS: Pitching angle plus a small time difference (TSTEP) """ Nc = self.BF.x_mid.shape[0] Ns = self.BF.x_mid.shape[1] if i == 0: x_col = self.BF.x_mid y_col = self.BF.y_mid z_col = self.BF.z_mid # Panel midpoint velocity calculations # Calculating the surface positions at tplus(tp) and tminus(tm) (xtpneut, ytpneut, ztpneut) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, 0, 0, 0) (xtpdp_y, ytpdp_y, ztpdp_y) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, 0, DSTEP2, DSTEP3) (xtpdp_x, ytpdp_x, ztpdp_x) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, DSTEP1, 0, 0) (xtpdm_y, ytpdm_y, ztpdm_y) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, 0, -DSTEP2, -DSTEP3) (xtpdm_x, ytpdm_x, ztpdm_x) = self.neutral_plane(x_col, y_col, T, THETA_PLUS, HEAVE_PLUS, -DSTEP1, 0, 0) (xtmneut, ytmneut, ztmneut) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, 0, 0, 0) (xtmdp_y, ytmdp_y, ztmdp_y) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, 0, DSTEP2, DSTEP3) (xtmdp_x, ytmdp_x, ztmdp_x) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, DSTEP1, 0, 0) (xtmdm_y, ytmdm_y, ztmdm_y) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, 0, -DSTEP2, -DSTEP3) (xtmdm_x, ytmdm_x, ztmdm_x) = self.neutral_plane(x_col, y_col, T, THETA_MINUS, HEAVE_MINUS, -DSTEP1, 0, 0) # Displaced airfoil's panel midpoints for times tplus(tp) and tminus(tm) xctp = xtpneut + point_vectors(Nc, Ns, (xtpdp_y, ytpdp_y, ztpdp_y), (xtpdm_y, ytpdm_y, ztpdm_y), (xtpdp_x, ytpdp_x, ztpdp_x), (xtpdm_x, ytpdm_x, ztpdm_x))[0]*z_col xctm = xtmneut + point_vectors(Nc, Ns, (xtmdp_y, ytmdp_y, ztmdp_y), (xtmdm_y, ytmdm_y, ztmdm_y), (xtmdp_x, ytmdp_x, ztmdp_x), (xtmdm_x, ytmdm_x, ztmdm_x))[0]*z_col yctp = ytpneut + point_vectors(Nc, Ns, (xtpdp_y, ytpdp_y, ztpdp_y), (xtpdm_y, ytpdm_y, ztpdm_y), (xtpdp_x, ytpdp_x, ztpdp_x), (xtpdm_x, ytpdm_x, ztpdm_x))[1]*z_col yctm = ytmneut + point_vectors(Nc, Ns, (xtmdp_y, ytmdp_y, ztmdp_y), (xtmdm_y, ytmdm_y, ztmdm_y), (xtmdp_x, ytmdp_x, ztmdp_x), (xtmdm_x, ytmdm_x, ztmdm_x))[1]*z_col zctp = ztpneut + point_vectors(Nc, Ns, (xtpdp_y, ytpdp_y, ztpdp_y), (xtpdm_y, ytpdm_y, ztpdm_y), (xtpdp_x, ytpdp_x, ztpdp_x), (xtpdm_x, ytpdm_x, ztpdm_x))[2]*z_col zctm = ztmneut + point_vectors(Nc, Ns, (xtmdp_y, ytmdp_y, ztmdp_y), (xtmdm_y, ytmdm_y, ztmdm_y), (xtmdp_x, ytmdp_x, ztmdp_x), (xtmdm_x, ytmdm_x, ztmdm_x))[2]*z_col # Velocity calculations on the surface panel midpoints self.vx = (xctp - xctm)/(2*TSTEP) self.vy = (yctp - yctm)/(2*TSTEP) self.vz = (zctp - zctm)/(2*TSTEP) elif i == 1: # First-order backwards differencing of body collocation point positions self.vx = (self.AF.x_mid[:,:,0]-self.AF.x_mid[:,:,1])/DEL_T - self.V0 self.vy = (self.AF.y_mid[:,:,0]-self.AF.y_mid[:,:,1])/DEL_T self.vz = (self.AF.z_mid[:,:,0]-self.AF.z_mid[:,:,1])/DEL_T else: # Second-order backwards differencing of body collocation point positions self.vx = (3*self.AF.x_mid[:,:,0]-4*self.AF.x_mid[:,:,1]+self.AF.x_mid[:,:,2])/(2*DEL_T) - self.V0 self.vy = (3*self.AF.y_mid[:,:,0]-4*self.AF.y_mid[:,:,1]+self.AF.y_mid[:,:,2])/(2*DEL_T) self.vz = (3*self.AF.z_mid[:,:,0]-4*self.AF.z_mid[:,:,1]+self.AF.z_mid[:,:,2])/(2*DEL_T) # # Body source strengths with normal vector pointing outward (overall sigma pointing outward) (nx, ny, nz) = panel_vectors(self.AF.x, self.AF.y, self.AF.z)[0:3] self.sigma = nx*(self.V0 + self.vx) + ny*self.vy + nz*self.vz
def wake_rollup(Swimmers, DEL_T, i): """Performs wake rollup on the swimmers' wake panels. Args: Swimmers: List of Swimmer objects being simulated. DEL_T: Time step length. i: Time step number. """ # Wake panels initialize when i==1 if i == 0: pass else: NT = i # Number of targets (wake panel points that are rolling up) for SwimT in Swimmers: SwimT.Wake.vx = np.zeros(NT) SwimT.Wake.vz = np.zeros(NT) DELTA_CORE = SwimT.DELTA_CORE for SwimI in Swimmers: # Coordinate transformation for body panels influencing wake (xp1, xp2, zp) = transformation(SwimT.Wake.x[1:i+1], SwimT.Wake.z[1:i+1], SwimI.Body.AF.x, SwimI.Body.AF.z) # Angle of normal vector with respect to global z-axis (nx, nz) = panel_vectors(SwimI.Body.AF.x, SwimI.Body.AF.z)[2:4] beta = np.arctan2(-nx, nz) # Katz-Plotkin eqns 10.20 and 10.21 for body source influence dummy1 = np.log((xp1**2+zp**2)/(xp2**2+zp**2))/(4*np.pi) dummy2 = (np.arctan2(zp,xp2)-np.arctan2(zp,xp1))/(2*np.pi) # Rotate back to global coordinates dummy3 = dummy1*np.cos(beta) - dummy2*np.sin(beta) dummy4 = dummy1*np.sin(beta) + dummy2*np.cos(beta) # Finish eqns 10.20 and 10.21 for induced velocity by multiplying with sigma SwimT.Wake.vx += np.dot(dummy3, SwimI.Body.sigma) SwimT.Wake.vz += np.dot(dummy4, SwimI.Body.sigma) # Formation of (x-x0) and (z-z0) matrices, similar to xp1/xp2/zp but coordinate transformation is not necessary NI = SwimI.Body.N+1 xp = np.repeat(SwimT.Wake.x[1:i+1,np.newaxis], NI, 1) - np.repeat(SwimI.Body.AF.x[:,np.newaxis].T, NT, 0) zp = np.repeat(SwimT.Wake.z[1:i+1,np.newaxis], NI, 1) - np.repeat(SwimI.Body.AF.z[:,np.newaxis].T, NT, 0) # Find distance r_b between each influence/target r_b = np.sqrt(xp**2+zp**2) # Katz-Plotkin eqns 10.9 and 10.10 for body doublet (represented as point vortices) influence dummy1 = zp/(2*np.pi*(r_b**2+DELTA_CORE**2)) dummy2 = -xp/(2*np.pi*(r_b**2+DELTA_CORE**2)) # Finish eqns 10.9 and 10.10 by multiplying with Body.gamma, add to induced velocity SwimT.Wake.vx += np.dot(dummy1, SwimI.Body.gamma) SwimT.Wake.vz += np.dot(dummy2, SwimI.Body.gamma) # Formation of (x-x0) and (z-z0) matrices, similar to xp1/xp2/zp but coordinate transformation is not necessary NI = SwimI.Edge.N+1 xp = np.repeat(SwimT.Wake.x[1:i+1,np.newaxis], NI, 1) - np.repeat(SwimI.Edge.x[:,np.newaxis].T, NT, 0) zp = np.repeat(SwimT.Wake.z[1:i+1,np.newaxis], NI, 1) - np.repeat(SwimI.Edge.z[:,np.newaxis].T, NT, 0) # Find distance r_e between each influence/target r_e = np.sqrt(xp**2+zp**2) # Katz-Plotkin eqns 10.9 and 10.10 for edge (as point vortices) influence dummy1 = zp/(2*np.pi*(r_e**2+DELTA_CORE**2)) dummy2 = -xp/(2*np.pi*(r_e**2+DELTA_CORE**2)) # Finish eqns 10.9 and 10.10 by multiplying with Edge.gamma, add to induced velocity SwimT.Wake.vx += np.dot(dummy1, SwimI.Edge.gamma) SwimT.Wake.vz += np.dot(dummy2, SwimI.Edge.gamma) # Formation of (x-x0) and (z-z0) matrices, similar to xp1/xp2/zp but coordinate transformation is not necessary NI = i+1 xp = np.repeat(SwimT.Wake.x[1:i+1,np.newaxis], NI, 1) - np.repeat(SwimI.Wake.x[:i+1,np.newaxis].T, NT, 0) zp = np.repeat(SwimT.Wake.z[1:i+1,np.newaxis], NI, 1) - np.repeat(SwimI.Wake.z[:i+1,np.newaxis].T, NT, 0) # Find distance r_w between each influence/target r_w = np.sqrt(xp**2+zp**2) # Katz-Plotkin eqns 10.9 and 10.10 for wake (as point vortices) influence dummy1 = zp/(2*np.pi*(r_w**2+DELTA_CORE**2)) dummy2 = -xp/(2*np.pi*(r_w**2+DELTA_CORE**2)) # Finish eqns 10.9 and 10.10 by multiplying with Wake.gamma, add to induced velocity SwimT.Wake.vx += np.dot(dummy1, SwimI.Wake.gamma[:i+1]) SwimT.Wake.vz += np.dot(dummy2, SwimI.Wake.gamma[:i+1]) for Swim in Swimmers: # Modify wake with the total induced velocity Swim.Wake.x[1:i+1] += Swim.Wake.vx*DEL_T Swim.Wake.z[1:i+1] += Swim.Wake.vz*DEL_T
def pressure(self, P, i, stencil_npts=5): """Calculates the pressure distribution along the body's surface. Args: RHO: Fluid density. DEL_T: Time step length. i: Time step number. """ RHO = P['RHO'] DEL_T = P['DEL_T'] SW_4PRESSURE = P['SW_4PRESSURE'] (tx, tz, nx, nz, lpanel) = panel_vectors(self.AF.x, self.AF.z) # Defining stencil if (stencil_npts == 5): stencil_chordwise = np.zeros((self.N, 5), dtype=int) stencil_chordwise[0:2, :] = np.array([[0, 1, 2, 3, 4], [-1, 0, 1, 2, 3]]) stencil_chordwise[2:-2, :] = np.tile(np.array([-2, -1, 0, 1, 2]), (self.N - 4, 1)) stencil_chordwise[-2:, :] = np.array([[-3, -2, -1, 0, 1], [-4, -3, -2, -1, 0]]) else: if (stencil_npts != 3): print '+-----------------------------------------------------------------------------+' print '| WARNING! There are only three- and five-point stencils available. |' print '| Defaulting to the three-point stencil. |' print '+-----------------------------------------------------------------------------+' stencil_chordwise = np.zeros((self.N, 3)) stencil_chordwise[0, :] = np.array([0, 1, 2]) stencil_chordwise[1:-1, :] = np.repeat(np.array([-1, 0, 1]), self.N - 2, axis=0) stencil_chordwise[-1, :] = np.array([-2, -1, 0]) # Tangential panel velocity dmu/dl dmu_dl = np.empty(self.N) for j in xrange(self.N): # Defining stencil depending on chordwise element stencil = np.copy(stencil_chordwise[j, :]) pan_elem = j + stencil # Calling finite difference approximations based on stencil (3-point and 5-point available) dmu_dl[j] = finite_diff(self.mu[pan_elem], lpanel[pan_elem], stencil) # Potential change dmu/dt, second-order differencing after first time step if i == 0: dmu_dt = self.mu / DEL_T elif i == 1: dmu_dt = (self.mu - self.mu_past[0, :]) / DEL_T elif (i > 3 and SW_4PRESSURE): dmu_dt = ((25. / 12.) * self.mu - 4. * self.mu_past[0, :] + 3. * self.mu_past[1, :] - (4. / 3.) * self.mu_past[2, :] + (1. / 4.) * self.mu_past[3, :]) / DEL_T else: dmu_dt = (3. * self.mu - 4. * self.mu_past[0, :] + self.mu_past[1, :]) / (2. * DEL_T) # Unsteady pressure calculation (from Matlab code) qpx_tot = dmu_dl * tx + self.sigma * nx qpz_tot = dmu_dl * tz + self.sigma * nz self.p_s = -RHO * (qpx_tot**2 + qpz_tot**2) / 2. self.p_us = RHO * dmu_dt + RHO * (qpx_tot * (self.V + self.vx) + qpz_tot * self.vz) self.p = self.p_s + self.p_us self.cp = self.p / (0.5 * RHO * self.V**2)
def setInterfaceForce(self, Solid, Body, PyFEA, THETA, HEAVE, outerCorr, SW_VISC_DRAG, delFs, SW_INTERP_MTD, C, i_t): """ Updates the structural mesh position, calculates the traction forces on the free nodes, and determines the initial condisitons for the timestep. Args: Solid (object): A solid object created from the solid class. Body (object): A body object created from the swimmer class. PyFEA (object): A FEA solver object created from the PyFEA class. t (float): Current simulation time. TSTEP (flaot): Small, incremental distance/time offsets. outerCorr (int): Current FSI subiteration number. SW_VISC_DRAG (bool): Used to determine if viscous forces should be included. delFs (float): NumPy array of viscous force components. SW_INTERP_MTD (bool): Used to determine if linear or cubic spline interpolation between fluid and solid domains should be used. C (float): Body chord length. i_t (int): Current time-step number. """ # Superposing the structural displacements if (outerCorr > 1): Solid.nodes[:, 0] += (self.nodeDispl[:, 0] - self.nodeDisplOld[:, 0]) Solid.nodes[:, 1] += (self.nodeDispl[:, 1] - self.nodeDisplOld[:, 1]) if (outerCorr <= 1): # Updating the new kinematics Solid.nodes[:, 0] = (Solid.nodesNew[:, 0] - Solid.nodesNew[0, 0]) * np.cos(THETA) Solid.nodes[:, 1] = HEAVE + (Solid.nodesNew[:, 0] - Solid.nodesNew[0, 0]) * np.sin(THETA) # Calculating the shift in node positions with the swimming velocity nodeDelxp = Body.AF.x_le * np.ones((Solid.Nelements + 1, 1)) # nodeDelzp = Body.AF.z_le * np.ones((Solid.Nelements + 1,1)) #Superposiing the kinematics and swimming translations Solid.nodes[:, 0] = Solid.nodes[:, 0] + nodeDelxp.T # Solid.nodes[:,1] = Solid.nodes[:,1] + nodeDelzp.T # Determine the load conditons from the fluid solver # Calculate the panel lengths and normal vectors (nx, nz, lp) = panel_vectors(Body.AF.x, Body.AF.z)[2:5] # Calculate the force magnitude acting on the panel due to pressure, # then calculate the x-z components of this force magPF = Body.p * lp * 1. pF = np.zeros((Body.N, 2)) if (SW_VISC_DRAG == 1): pF[:, 0] = (magPF.T * nx.T * -1.) + delFs[:, 0] pF[:, 1] = (magPF.T * nz.T * -1.) + delFs[:, 1] else: pF[:, 0] = magPF.T * nx.T * -1. pF[:, 1] = magPF.T * nz.T * -1. # Determine the moment arm between top and bottom panel points, and # collapse force and moments to the camber line colM = np.zeros((0.5 * Body.N, 1)) colPF = np.zeros((0.5 * Body.N, 2)) meanPt = np.zeros((0.5 * Body.N, 2)) for i in xrange(int(0.5 * Body.N)): meanPt[i, 0] = 0.5 * (Body.AF.x_mid[0, i] + Body.AF.x_mid[0, -(i + 1)]) meanPt[i, 1] = 0.5 * (Body.AF.z_mid[0, i] + Body.AF.z_mid[0, -(i + 1)]) colPF[i, :] = pF[i, :] + pF[-(i + 1), :] colM[i,:] = -1. * pF[i,0] * (Body.AF.z_mid[0,i] - meanPt[i,1]) + \ pF[i,1] * (Body.AF.x_mid[0,i] - meanPt[i,0]) + \ -1. * pF[-(i+1),0] * (Body.AF.z_mid[0,-(i+1)] - meanPt[i,1]) + \ pF[-(i+1),1] * (Body.AF.x_mid[0,-(i+1)] - meanPt[i,0]) colPF = np.flipud(colPF) colM = np.flipud(colM) # Interpolate the collapsed forces and moments onto the structural mesh nodalInput = np.zeros((Solid.Nnodes, 6)) if (SW_INTERP_MTD == True): f1 = extrap1d( interp1d(Solid.meanline_c0[0.5 * Body.N:], colPF[:, 0])) f2 = extrap1d( interp1d(Solid.meanline_c0[0.5 * Body.N:], colPF[:, 1])) f3 = extrap1d( interp1d(Solid.meanline_c0[0.5 * Body.N:], colM[:, 0])) nodalInput[:, 0] = f1(Solid.nodes[:, 2]) nodalInput[:, 1] = f2(Solid.nodes[:, 2]) nodalInput[:, 5] = f3(Solid.nodes[:, 2]) # nodalInput[:,0] = np.interp(Solid.nodes[:,2], Solid.meanline_c0[0.5*Body.N:], colPF[:,0], left=0, right=0) # nodalInput[:,1] = np.interp(Solid.nodes[:,2], Solid.meanline_c0[0.5*Body.N:], colPF[:,1], left=0, right=0) # nodalInput[:,5] = np.interp(Solid.nodes[:,2], Solid.meanline_c0[0.5*Body.N:], colM[:,0], left=0, right=0) else: nodalInput[:, 0] = spline(Solid.meanline_c0[0.5 * Body.N:], colPF[:, 0], Solid.nodes[:, 2]) nodalInput[:, 1] = spline(Solid.meanline_c0[0.5 * Body.N:], colPF[:, 1], Solid.nodes[:, 2]) nodalInput[:, 5] = spline(Solid.meanline_c0[0.5 * Body.N:], colM[:, 0], Solid.nodes[:, 2]) # Rotate force components into the relative cooridnate system (nodalInput[:, 0], nodalInput[:, 1]) = self.rotatePts(nodalInput[:, 0], nodalInput[:, 1], -THETA) # Create the load matrix Fload = np.zeros((3 * (Solid.Nnodes), 1)) Fload[0::3, 0] = np.copy(nodalInput[:, 0]) Fload[1::3, 0] = np.copy(nodalInput[:, 1]) Fload[2::3, 0] = np.copy(nodalInput[:, 5]) # Create element area matrix A = np.copy(Solid.tBeamStruct[:, 0]) # Create area moment of inertia matrix I = 1. * Solid.tBeamStruct[:, 0]**3 / 12 # Initial element length l_0 = C / Solid.Nelements # Initial displacements and velocities # if (i_t <= 1 and outerCorr <= 1): temp = 3 * Solid.fixedCounter if (i_t <= 1 and outerCorr <= 1): PyFEA.U_n = np.zeros((3 * Solid.Nnodes, 1)) PyFEA.Udot_n = np.zeros((3 * Solid.Nnodes, 1)) PyFEA.UdotDot_n = np.zeros((3 * Solid.Nnodes - temp, 1)) PyFEA.U_nPlus = np.zeros((3 * Solid.Nnodes - temp, 1)) PyFEA.Udot_nPlus = np.zeros((3 * Solid.Nnodes - temp, 1)) PyFEA.UdotDot_nPlus = np.zeros((3 * Solid.Nnodes - temp, 1)) elif (i_t > 0 and outerCorr <= 1): PyFEA.U_n = np.zeros((3 * Solid.Nnodes, 1)) PyFEA.Udot_n = np.zeros((3 * Solid.Nnodes, 1)) PyFEA.UdotDot_n = np.zeros((3 * Solid.Nnodes - temp, 1)) PyFEA.U_n[temp:, 0] = PyFEA.U_nPlus.T PyFEA.Udot_n[temp:, 0] = PyFEA.Udot_nPlus.T PyFEA.UdotDot_n = PyFEA.Udot_nPlus PyFEA.Fload = np.copy(Fload) PyFEA.A = np.copy(A) PyFEA.I = np.copy(I) PyFEA.l = l_0 * np.ones(Solid.Nelements)
def setSpringForce(self, Body, Solid, PyFEA, P, outerCorr, delFs, i_t): # Superposing the structural displacements if (outerCorr > 1): Solid.nodes[:, 0] += (self.nodeDispl[:, 0] - self.nodeDisplOld[:, 0]) Solid.nodes[:, 1] += (self.nodeDispl[:, 1] - self.nodeDisplOld[:, 1]) if (outerCorr <= 1): # Updating the new kinematics Solid.nodes[:, 0] = (Solid.nodesNew[:, 0] - Solid.nodesNew[0, 0]) * np.cos(P['THETA'][i_t]) Solid.nodes[:, 1] = P['HEAVE'][i_t] + (Solid.nodesNew[:, 0] - Solid.nodesNew[0, 0]) * np.sin( P['THETA'][i_t]) # Calculating the shift in node positions with the swimming velocity nodeDelxp = Body.AF.x_le * np.ones((Solid.Nelements + 1, 1)) # nodeDelzp = Body.AF.z_le * np.ones((Solid.Nelements + 1,1)) #Superposiing the kinematics and swimming translations Solid.nodes[:, 0] = Solid.nodes[:, 0] + nodeDelxp.T # Solid.nodes[:,1] = Solid.nodes[:,1] + nodeDelzp.T # Determine the load conditons from the fluid solver # Calculate the panel lengths and normal vectors (nx, nz, lp) = panel_vectors(Body.AF.x, Body.AF.z)[2:5] # Calculate the force magnitude acting on the panel due to pressure, # then calculate the x-z components of this force magPF = Body.p * lp * 1. pF = np.zeros((Body.N, 3)) if P['SW_VISC_DRAG']: pF[:, 0] = (magPF.T * nx.T * -1.) + delFs[:, 0] pF[:, 2] = (magPF.T * nz.T * -1.) + delFs[:, 1] else: pF[:, 0] = magPF.T * nx.T * -1. pF[:, 2] = magPF.T * nz.T * -1. # Calculating the moment about the leading edge r = np.zeros((Body.N, 3)) r[:, 0] = Body.AF.x_mid[0, :].T - Body.AF.x_le r[:, 2] = Body.AF.z_mid[0, :].T - Body.AF.z_le delM_le = np.cross(r, pF) Nf = -np.sum(delM_le[:, 1]) # Calculating the inertial torque resulting from the vertical # acceleration of the leading edge Ni = -P['RHO_S'] * Solid.tmax * P['C']**2 * np.pi**2 * P[ 'F']**2 * np.cos(PyFEA.theta_n) * P['INERTIA'][i_t] # Calculating the moment of intertia about the leading edge. An extra # 0.5 is multiplied in to approximate the teardrop geometry's mass. I = 0.5 * PyFEA.RHO_S * Solid.tmax * P['C']**3 / 3. # Define spring and dampening constants kappa_1 = P['KAPPA_1'] kappa_2 = P['KAPPA_2'] zeta = P['ZETA'] if (i_t <= 1 and outerCorr <= 1): PyFEA.theta_n = 0. PyFEA.thetaDot_n = 0. PyFEA.thetaDotDot_n = 0. PyFEA.theta_nPlus = 0. PyFEA.thetaDot_nPlus = 0. PyFEA.thetaDotDot_nPlus = 0. elif (i_t > 0 and outerCorr <= 1): PyFEA.theta_n = 0. PyFEA.thetaDot_n = 0. PyFEA.thetaDotDot_n = 0. PyFEA.theta_n = np.copy(PyFEA.theta_nPlus) PyFEA.thetaDot_n = np.copy(PyFEA.thetaDot_nPlus) PyFEA.thetaDotDot_n = np.copy(PyFEA.thetaDotDot_nPlus) PyFEA.I = np.copy(I) PyFEA.kappa_1 = np.copy(kappa_1) PyFEA.kappa_2 = np.copy(kappa_2) PyFEA.zeta = np.copy(zeta) PyFEA.Nf = np.copy(Nf) PyFEA.Ni = np.copy(Ni)