def eval(Zeta00, Zeta01, Zeta02, Zeta03, Uc): ''' Returns a 4 x 3 array, containing the derivative of Wnc*Uc w.r.t the panel vertices coordinates. ''' R02 = Zeta02 - Zeta00 R13 = Zeta03 - Zeta01 crR02R13 = libalg.cross3d(R02, R13) norm_crR02R13 = np.linalg.norm(crR02R13) cub_crR02R13 = norm_crR02R13**3 Acr = libalg.cross3d(crR02R13, R13) Bcr = libalg.cross3d(crR02R13, R02) Cdot = np.dot(crR02R13, Uc) uc_x, uc_y, uc_z = Uc r02_x, r02_y, r02_z = R02 r13_x, r13_y, r13_z = R13 crR13Uc_x, crR13Uc_y, crR13Uc_z = libalg.cross3d(R13, Uc) crR02Uc_x, crR02Uc_y, crR02Uc_z = libalg.cross3d(R02, Uc) crR02R13_x, crR02R13_y, crR02R13_z = crR02R13 Acr_x, Acr_y, Acr_z = Acr Bcr_x, Bcr_y, Bcr_z = Bcr # dUnorm_dR.shape=(2,3) dUnorm_dR = np.array( [[ Acr_x * Cdot / cub_crR02R13 + crR13Uc_x / norm_crR02R13, Acr_y * Cdot / cub_crR02R13 + crR13Uc_y / norm_crR02R13, Acr_z * Cdot / cub_crR02R13 + crR13Uc_z / norm_crR02R13 ], [ -Bcr_x * Cdot / cub_crR02R13 - crR02Uc_x / norm_crR02R13, -Bcr_y * Cdot / cub_crR02R13 - crR02Uc_y / norm_crR02R13, -Bcr_z * Cdot / cub_crR02R13 - crR02Uc_z / norm_crR02R13 ]]) # Allocate dUnorm_dZeta = np.zeros((4, 3)) for vv in range(4): # loop through panel vertices for cc_zeta in range(3): # loop panel vertices component for rr in range(2): # loop segments R02, R13 for cc_rvec in range(3): # loop segment component dUnorm_dZeta[vv,cc_zeta]+=\ dUnorm_dR[rr,cc_rvec]*dR_dZeta[vv,cc_zeta,rr,cc_rvec] return dUnorm_dZeta
def eval_seg_comp_loop(DerP, DerA, DerB, ZetaP, ZetaA, ZetaB, gamma_seg): ''' Derivative of induced velocity Q w.r.t. collocation and segment coordinates in format: [ (x,y,z) of Q, (x,y,z) of Zeta ] Warning: function optimised for performance. Variables are scaled during the execution. ''' Cfact = cfact_biot * gamma_seg RA = ZetaP - ZetaA RB = ZetaP - ZetaB RAB = ZetaB - ZetaA Vcr = libalg.cross3d(RA, RB) vcr2 = np.dot(Vcr, Vcr) # numerical radious if vcr2 < (VORTEX_RADIUS_SQ * libalg.normsq3d(RAB)): return ### other constants ra1, rb1 = libalg.norm3d(RA), libalg.norm3d(RB) rainv = 1. / ra1 rbinv = 1. / rb1 Tv = RA * rainv - RB * rbinv dotprod = np.dot(RAB, Tv) ### --------------------------------------------- cross-product derivatives # lower triangular part only vcr2inv = 1. / vcr2 vcr4inv = vcr2inv * vcr2inv diag_fact = Cfact * vcr2inv * dotprod off_fact = -2. * Cfact * vcr4inv * dotprod Dvcross = [[diag_fact + off_fact * Vcr[0]**2], [off_fact * Vcr[0] * Vcr[1], diag_fact + off_fact * Vcr[1]**2], [ off_fact * Vcr[0] * Vcr[2], off_fact * Vcr[1] * Vcr[2], diag_fact + off_fact * Vcr[2]**2 ]] ### ------------------------------------------ difference terms derivatives Vsc = Vcr * vcr2inv * Cfact Ddiff = np.array([RAB * Vsc[0], RAB * Vsc[1], RAB * Vsc[2]]) dQ_dRAB = np.array([Tv * Vsc[0], Tv * Vsc[1], Tv * Vsc[2]]) ### ---------------------------------------------- Final assembly (crucial) # ps: calling Dvcross_by_skew3d does not slow down execution. dQ_dRA=Dvcross_by_skew3d(Dvcross,-RB)\ +np.dot(Ddiff, der_runit(RA,rainv,-rainv**3)) dQ_dRB=Dvcross_by_skew3d(Dvcross, RA)\ -np.dot(Ddiff, der_runit(RB,rbinv,-rbinv**3)) DerP += dQ_dRA + dQ_dRB # w.r.t. P DerA -= dQ_dRAB + dQ_dRA # w.r.t. A DerB += dQ_dRAB - dQ_dRB # w.r.t. B
def joukovski_qs_segment(zetaA,zetaB,v_mid,gamma=1.0,fact=0.5): ''' Joukovski force over vetices A and B produced by the segment A->B. The factor fact allows to compute directly the contribution over the vertices A and B (e.g. 0.5) or include DENSITY. ''' rab=zetaB-zetaA fs=libalg.cross3d(v_mid,rab) gfact=fact*gamma return gfact*fs
def panel_normal(ZetaPanel): ''' return normal of panel with vertiex coordinates ZetaPanel, where: ZetaPanel.shape=(4,3) ''' # build cross-vectors r02=ZetaPanel[2,:]-ZetaPanel[0,:] r13=ZetaPanel[3,:]-ZetaPanel[1,:] nvec=libalg.cross3d(r02,r13) nvec=nvec/libalg.norm3d(nvec) return nvec
def biot_segment(zetaP,zetaA,zetaB,gamma=1.0): ''' Induced velocity of segment A_>B of circulation gamma over point P. ''' # differences ra=zetaP-zetaA rb=zetaP-zetaB rab=zetaB-zetaA ra_norm,rb_norm=libalg.norm3d(ra),libalg.norm3d(rb) vcross=libalg.cross3d(ra,rb) vcross_sq=np.dot(vcross,vcross) # numerical radious if vcross_sq<(VORTEX_RADIUS_SQ*libalg.normsq3d(rab)): return np.zeros((3,)) q=((cfact_biot*gamma/vcross_sq)*\ ( np.dot(rab,ra)/ra_norm - np.dot(rab,rb)/rb_norm)) * vcross return q
def biot_panel_fast(zetaC,ZetaPanel,gamma=1.0): ''' Induced velocity over point ZetaC of a panel of vertices coordinates ZetaPanel and circulaiton gamma, where: ZetaPanel.shape=(4,3)=[vertex local number, (x,y,z) component] ''' Cfact=cfact_biot*gamma q=np.zeros((3,)) R_list = zetaC-ZetaPanel Runit_list=[R_list[ii]/libalg.norm3d(R_list[ii]) for ii in svec] for aa,bb in LoopPanel: RAB=ZetaPanel[bb,:]-ZetaPanel[aa,:] # segment vector Vcr = libalg.cross3d(R_list[aa],R_list[bb]) vcr2=np.dot(Vcr,Vcr) if vcr2<(VORTEX_RADIUS_SQ*libalg.normsq3d(RAB)): continue q+=( (Cfact/vcr2)*np.dot(RAB,Runit_list[aa]-Runit_list[bb]) ) *Vcr return q
def eval_seg_exp_loop(DerP, DerA, DerB, ZetaP, ZetaA, ZetaB, gamma_seg): ''' Derivative of induced velocity Q w.r.t. collocation (DerC) and segment coordinates in format. To optimise performance, the function requires the derivative terms to be pre-allocated and passed as input. Each Der* term returns derivatives in the format [ (x,y,z) of Zeta, (x,y,z) of Q] Warning: to optimise performance, variables are scaled during the execution. ''' RA = ZetaP - ZetaA RB = ZetaP - ZetaB RAB = ZetaB - ZetaA Vcr = libalg.cross3d(RA, RB) vcr2 = np.dot(Vcr, Vcr) # numerical radious vortex_radious_here = VORTEX_RADIUS * libalg.norm3d(RAB) if vcr2 < vortex_radious_here**2: return # scaling ra1, rb1 = libalg.norm3d(RA), libalg.norm3d(RB) ra2, rb2 = ra1**2, rb1**2 rainv = 1. / ra1 rbinv = 1. / rb1 ra_dir, rb_dir = RA * rainv, RB * rbinv ra3inv, rb3inv = rainv**3, rbinv**3 Vcr = Vcr / vcr2 diff_vec = ra_dir - rb_dir vdot_prod = np.dot(diff_vec, RAB) T2 = vdot_prod / vcr2 # Extract components ra_x, ra_y, ra_z = RA rb_x, rb_y, rb_z = RB rab_x, rab_y, rab_z = RAB vcr_x, vcr_y, vcr_z = Vcr ra2_x, ra2_y, ra2_z = RA**2 rb2_x, rb2_y, rb2_z = RB**2 ra_vcr_x, ra_vcr_y, ra_vcr_z = 2. * libalg.cross3d(RA, Vcr) rb_vcr_x, rb_vcr_y, rb_vcr_z = 2. * libalg.cross3d(RB, Vcr) vcr_sca_x, vcr_sca_y, vcr_sca_z = Vcr * ra3inv vcr_scb_x, vcr_scb_y, vcr_scb_z = Vcr * rb3inv # # ### derivatives indices: # # # the 1st is the component of the vaiable w.r.t derivative are taken. # # # the 2nd is the component of the output dQ_dRA = np.array([ [ -vdot_prod * rb_vcr_x * vcr_x + vcr_sca_x * (rab_x * (ra2 - ra2_x) - ra_x * ra_y * rab_y - ra_x * ra_z * rab_z), -T2 * rb_z - vdot_prod * rb_vcr_x * vcr_y + vcr_sca_y * (rab_x * (ra2 - ra2_x) - ra_x * ra_y * rab_y - ra_x * ra_z * rab_z), T2 * rb_y - vdot_prod * rb_vcr_x * vcr_z + vcr_sca_z * (rab_x * (ra2 - ra2_x) - ra_x * ra_y * rab_y - ra_x * ra_z * rab_z) ], [ T2 * rb_z - vdot_prod * rb_vcr_y * vcr_x + vcr_sca_x * (rab_y * (ra2 - ra2_y) - ra_x * ra_y * rab_x - ra_y * ra_z * rab_z), -vdot_prod * rb_vcr_y * vcr_y + vcr_sca_y * (rab_y * (ra2 - ra2_y) - ra_x * ra_y * rab_x - ra_y * ra_z * rab_z), -T2 * rb_x - vdot_prod * rb_vcr_y * vcr_z + vcr_sca_z * (rab_y * (ra2 - ra2_y) - ra_x * ra_y * rab_x - ra_y * ra_z * rab_z) ], [ -T2 * rb_y - vdot_prod * rb_vcr_z * vcr_x + vcr_sca_x * (rab_z * (ra2 - ra2_z) - ra_x * ra_z * rab_x - ra_y * ra_z * rab_y), T2 * rb_x - vdot_prod * rb_vcr_z * vcr_y + vcr_sca_y * (rab_z * (ra2 - ra2_z) - ra_x * ra_z * rab_x - ra_y * ra_z * rab_y), -vdot_prod * rb_vcr_z * vcr_z + vcr_sca_z * (rab_z * (ra2 - ra2_z) - ra_x * ra_z * rab_x - ra_y * ra_z * rab_y) ] ]) dQ_dRB = np.array([[ vdot_prod * ra_vcr_x * vcr_x + vcr_scb_x * (rab_x * (-rb2 + rb2_x) + rab_y * rb_x * rb_y + rab_z * rb_x * rb_z), T2 * ra_z + vdot_prod * ra_vcr_x * vcr_y + vcr_scb_y * (rab_x * (-rb2 + rb2_x) + rab_y * rb_x * rb_y + rab_z * rb_x * rb_z), -T2 * ra_y + vdot_prod * ra_vcr_x * vcr_z + vcr_scb_z * (rab_x * (-rb2 + rb2_x) + rab_y * rb_x * rb_y + rab_z * rb_x * rb_z) ], [ -T2 * ra_z + vdot_prod * ra_vcr_y * vcr_x + vcr_scb_x * (rab_x * rb_x * rb_y + rab_y * (-rb2 + rb2_y) + rab_z * rb_y * rb_z), vdot_prod * ra_vcr_y * vcr_y + vcr_scb_y * (rab_x * rb_x * rb_y + rab_y * (-rb2 + rb2_y) + rab_z * rb_y * rb_z), T2 * ra_x + vdot_prod * ra_vcr_y * vcr_z + vcr_scb_z * (rab_x * rb_x * rb_y + rab_y * (-rb2 + rb2_y) + rab_z * rb_y * rb_z) ], [ T2 * ra_y + vdot_prod * ra_vcr_z * vcr_x + vcr_scb_x * (rab_x * rb_x * rb_z + rab_y * rb_y * rb_z + rab_z * (-rb2 + rb2_z)), -T2 * ra_x + vdot_prod * ra_vcr_z * vcr_y + vcr_scb_y * (rab_x * rb_x * rb_z + rab_y * rb_y * rb_z + rab_z * (-rb2 + rb2_z)), vdot_prod * ra_vcr_z * vcr_z + vcr_scb_z * (rab_x * rb_x * rb_z + rab_y * rb_y * rb_z + rab_z * (-rb2 + rb2_z)) ]]) dQ_dRAB = np.array( [[vcr_x * diff_vec[0], vcr_y * diff_vec[0], vcr_z * diff_vec[0]], [vcr_x * diff_vec[1], vcr_y * diff_vec[1], vcr_z * diff_vec[1]], [vcr_x * diff_vec[2], vcr_y * diff_vec[2], vcr_z * diff_vec[2]]]) DerP += (cfact_biot * gamma_seg) * (dQ_dRA + dQ_dRB).T # w.r.t. P DerA += (cfact_biot * gamma_seg) * (-dQ_dRAB - dQ_dRA).T # w.r.t. A DerB += (cfact_biot * gamma_seg) * (dQ_dRAB - dQ_dRB).T # w.r.t. B
def eval_panel_fast_coll(zetaP, ZetaPanel, gamma_pan=1.0): ''' Computes derivatives of induced velocity w.r.t. coordinates of target point, zetaP, coordinates. Returns two elements: - DerP: derivative of induced velocity w.r.t. ZetaP, with: DerP.shape=(3,3) : DerC[ Uind_{x,y,z}, ZetaC_{x,y,z} ] The function is based on eval_panel_fast, but does not perform operations required to compute the derivatives w.r.t. the panel coordinates. ''' DerP = np.zeros((3, 3)) ### ---------------------------------------------- Compute common variables # these are constants or variables depending only on vertices and P coords Cfact = cfact_biot * gamma_pan # distance vertex ii-th from P R_list = zetaP - ZetaPanel r1_list = [libalg.norm3d(R_list[ii]) for ii in svec] r1inv_list = [1. / r1_list[ii] for ii in svec] Runit_list = [R_list[ii] * r1inv_list[ii] for ii in svec] Der_runit_list = [ der_runit(R_list[ii], r1inv_list[ii], -r1inv_list[ii]**3) for ii in svec ] ### ------------------------------------------------- Loop through segments for aa, bb in LoopPanel: RAB = ZetaPanel[bb, :] - ZetaPanel[aa, :] # segment vector Vcr = libalg.cross3d(R_list[aa], R_list[bb]) vcr2 = np.dot(Vcr, Vcr) if vcr2 < (VORTEX_RADIUS_SQ * libalg.normsq3d(RAB)): continue Tv = Runit_list[aa] - Runit_list[bb] dotprod = np.dot(RAB, Tv) ### ----------------------------------------- cross-product derivatives # lower triangular part only vcr2inv = 1. / vcr2 vcr4inv = vcr2inv * vcr2inv diag_fact = Cfact * vcr2inv * dotprod off_fact = -2. * Cfact * vcr4inv * dotprod Dvcross = [[diag_fact + off_fact * Vcr[0]**2], [ off_fact * Vcr[0] * Vcr[1], diag_fact + off_fact * Vcr[1]**2 ], [ off_fact * Vcr[0] * Vcr[2], off_fact * Vcr[1] * Vcr[2], diag_fact + off_fact * Vcr[2]**2 ]] ### ---------------------------------------- difference term derivative Vsc = Vcr * vcr2inv * Cfact Ddiff = np.array([RAB * Vsc[0], RAB * Vsc[1], RAB * Vsc[2]]) ### ------------------------------------------ Final assembly (crucial) # dQ_dRA=Dvcross_by_skew3d(Dvcross,-R_list[bb])\ # +np.dot(Ddiff, Der_runit_list[aa] ) # dQ_dRB=Dvcross_by_skew3d(Dvcross, R_list[aa])\ # -np.dot(Ddiff, Der_runit_list[bb] ) DerP += Dvcross_by_skew3d(Dvcross,RAB)+\ +np.dot(Ddiff, Der_runit_list[aa]-Der_runit_list[bb] ) return DerP
def eval_panel_fast(zetaP, ZetaPanel, gamma_pan=1.0): ''' Computes derivatives of induced velocity w.r.t. coordinates of target point, zetaP, and panel coordinates. Returns two elements: - DerP: derivative of induced velocity w.r.t. ZetaP, with: DerP.shape=(3,3) : DerC[ Uind_{x,y,z}, ZetaC_{x,y,z} ] - DerVertices: derivative of induced velocity wrt panel vertices, with: DerVertices.shape=(4,3,3) : DerVertices[ vertex number {0,1,2,3}, Uind_{x,y,z}, ZetaC_{x,y,z} ] The function is based on eval_panel_comp, but minimises operationsby recycling variables. ''' DerP = np.zeros((3, 3)) DerVertices = np.zeros((4, 3, 3)) ### ---------------------------------------------- Compute common variables # these are constants or variables depending only on vertices and P coords Cfact = cfact_biot * gamma_pan # distance vertex ii-th from P R_list = zetaP - ZetaPanel r1_list = [libalg.norm3d(R_list[ii]) for ii in svec] r1inv_list = [1. / r1_list[ii] for ii in svec] Runit_list = [R_list[ii] * r1inv_list[ii] for ii in svec] Der_runit_list = [ der_runit(R_list[ii], r1inv_list[ii], -r1inv_list[ii]**3) for ii in svec ] ### ------------------------------------------------- Loop through segments for aa, bb in LoopPanel: RAB = ZetaPanel[bb, :] - ZetaPanel[aa, :] # segment vector Vcr = libalg.cross3d(R_list[aa], R_list[bb]) vcr2 = np.dot(Vcr, Vcr) if vcr2 < (VORTEX_RADIUS_SQ * libalg.normsq3d(RAB)): continue Tv = Runit_list[aa] - Runit_list[bb] dotprod = np.dot(RAB, Tv) ### ----------------------------------------- cross-product derivatives # lower triangular part only vcr2inv = 1. / vcr2 vcr4inv = vcr2inv * vcr2inv diag_fact = Cfact * vcr2inv * dotprod off_fact = -2. * Cfact * vcr4inv * dotprod Dvcross = [[diag_fact + off_fact * Vcr[0]**2], [ off_fact * Vcr[0] * Vcr[1], diag_fact + off_fact * Vcr[1]**2 ], [ off_fact * Vcr[0] * Vcr[2], off_fact * Vcr[1] * Vcr[2], diag_fact + off_fact * Vcr[2]**2 ]] ### ---------------------------------------- difference term derivative Vsc = Vcr * vcr2inv * Cfact Ddiff = np.array([RAB * Vsc[0], RAB * Vsc[1], RAB * Vsc[2]]) ### ---------------------------------------------------- RAB derivative dQ_dRAB = np.array([Tv * Vsc[0], Tv * Vsc[1], Tv * Vsc[2]]) ### ------------------------------------------ Final assembly (crucial) dQ_dRA=Dvcross_by_skew3d(Dvcross,-R_list[bb])\ +np.dot(Ddiff, Der_runit_list[aa] ) dQ_dRB=Dvcross_by_skew3d(Dvcross, R_list[aa])\ -np.dot(Ddiff, Der_runit_list[bb] ) DerP += dQ_dRA + dQ_dRB # w.r.t. P DerVertices[aa, :, :] -= dQ_dRAB + dQ_dRA # w.r.t. A DerVertices[bb, :, :] += dQ_dRAB - dQ_dRB # w.r.t. B # ### collocation point only # DerP +=Dvcross_by_skew3d(Dvcross,RA-RB)+np.dot(Ddiff, # der_runit(RA,rainv,minus_rainv3)-der_runit(RB,rbinv,minus_rbinv3)) return DerP, DerVertices