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 = algebra.cross3(R02, R13) norm_crR02R13 = np.linalg.norm(crR02R13) cub_crR02R13 = norm_crR02R13**3 Acr = algebra.cross3(crR02R13, R13) Bcr = algebra.cross3(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 = algebra.cross3(R13, Uc) crR02Uc_x, crR02Uc_y, crR02Uc_z = algebra.cross3(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 extract_resultants(self, tstep=None): if tstep is None: tstep = self.data.structure.timestep_info[self.data.ts] applied_forces = self.data.structure.nodal_b_for_2_a_for(tstep.steady_applied_forces, tstep) applied_forces_copy = applied_forces.copy() gravity_forces_copy = tstep.gravity_forces.copy() for i_node in range(self.data.structure.num_node): applied_forces_copy[i_node, 3:6] += algebra.cross3(tstep.pos[i_node, :], applied_forces_copy[i_node, 0:3]) gravity_forces_copy[i_node, 3:6] += algebra.cross3(tstep.pos[i_node, :], gravity_forces_copy[i_node, 0:3]) totals = np.sum(applied_forces_copy + gravity_forces_copy, axis=0) return totals[0:3], totals[3:6]
def efficiency_local_aero2struct_forces(local_aero_forces, chi_g, cbg, force_efficiency, moment_efficiency, i_elem, i_local_node): r""" Maps the local aerodynamic forces at a given vertex to its corresponding structural node, introducing user-defined efficiency and constant value factors. .. math:: \mathbf{f}_{struct}^B &= \varepsilon^f_0 C^{BG}\mathbf{f}_{i,aero}^G + \varepsilon^f_1\\ \mathbf{m}_{struct}^B &= \varepsilon^m_0 (C^{BG}(\mathbf{m}_{i,aero}^G + \tilde{\boldsymbol{\zeta}}^G\mathbf{f}_{i, aero}^G)) + \varepsilon^m_1 Args: local_aero_forces (np.ndarray): aerodynamic forces and moments at a grid vertex chi_g (np.ndarray): vector between grid vertex and structural node in inertial frame cbg (np.ndarray): transformation matrix between inertial and body frames of reference force_efficiency (np.ndarray): force efficiency matrix for all structural elements. Its size is ``n_elem x n_node_elem x 2 x 3`` moment_efficiency (np.ndarray): moment efficiency matrix for all structural elements. Its size is ``n_elem x n_node_elem x 2 x 3`` i_elem (int): element index i_local_node (int): local node index within element Returns: np.ndarray: corresponding aerodynamic force at the structural node from the force and moment at a grid vertex """ local_struct_forces = np.zeros(6) local_struct_forces[0:3] += np.dot(cbg, local_aero_forces[0:3]) * force_efficiency[i_elem, i_local_node, 0] # element wise multiplication local_struct_forces[0:3] += force_efficiency[i_elem, i_local_node, 1] local_struct_forces[3:6] += np.dot(cbg, local_aero_forces[3:6]) * moment_efficiency[i_elem, i_local_node, 0] local_struct_forces[3:6] += np.dot(cbg, algebra.cross3(chi_g, local_aero_forces[0:3])) * moment_efficiency[i_elem, i_local_node, 0] local_struct_forces[3:6] += moment_efficiency[i_elem, i_local_node, 1] return local_struct_forces
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] / algebra.norm3d(R_list[ii]) for ii in svec] for aa, bb in LoopPanel: RAB = ZetaPanel[bb, :] - ZetaPanel[aa, :] # segment vector Vcr = algebra.cross3(R_list[aa], R_list[bb]) vcr2 = np.dot(Vcr, Vcr) if vcr2 < (VORTEX_RADIUS_SQ * algebra.normsq3d(RAB)): continue q += ((Cfact / vcr2) * np.dot(RAB, Runit_list[aa] - Runit_list[bb])) * Vcr return q
def local_aero2struct_forces(local_aero_forces, chi_g, cbg, force_efficiency=None, moment_efficiency=None, i_elem=None, i_local_node=None): r""" Maps the local aerodynamic forces at a given vertex to its corresponding structural node. .. math:: \mathbf{f}_{struct}^B &= C^{BG}\mathbf{f}_{i,aero}^G\\ \mathbf{m}_{struct}^B &= C^{BG}(\mathbf{m}_{i,aero}^G + \tilde{\boldsymbol{\zeta}}^G\mathbf{f}_{i, aero}^G) Args: local_aero_forces (np.ndarray): aerodynamic forces and moments at a grid vertex chi_g (np.ndarray): vector between grid vertex and structural node in inertial frame cbg (np.ndarray): transformation matrix between inertial and body frames of reference force_efficiency (np.ndarray): Unused. See :func:`~sharpy.aero.utils.mapping.efficiency_local_aero2struct_forces`. moment_efficiency (np.ndarray): Unused. See :func:`~sharpy.aero.utils.mapping.efficiency_local_aero2struct_forces`. i_elem (int): Returns: np.ndarray: corresponding aerodynamic force at the structural node from the force and moment at a grid vertex """ local_struct_forces = np.zeros(6) local_struct_forces[0:3] += np.dot(cbg, local_aero_forces[0:3]) local_struct_forces[3:6] += np.dot(cbg, local_aero_forces[3:6]) local_struct_forces[3:6] += np.dot(cbg, algebra.cross3(chi_g, local_aero_forces[0:3])) return local_struct_forces
def eval_seg_comp_loop(DerP, DerA, DerB, ZetaP, ZetaA, ZetaB, gamma_seg, vortex_radius): """ 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. """ vortex_radius_sq = vortex_radius * vortex_radius Cfact = cfact_biot * gamma_seg RA = ZetaP - ZetaA RB = ZetaP - ZetaB RAB = ZetaB - ZetaA Vcr = algebra.cross3(RA, RB) vcr2 = np.dot(Vcr, Vcr) # numerical radious if vcr2 < (vortex_radius_sq * algebra.normsq3d(RAB)): return ### other constants ra1, rb1 = algebra.norm3d(RA), algebra.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 = algebra.cross3(v_mid, rab) gfact = fact * gamma return gfact * fs
def panel_normal(ZetaPanel): """ return normal of panel with vertex coordinates ZetaPanel, where: ZetaPanel.shape=(4,3) """ # build cross-vectors r02 = ZetaPanel[2, :] - ZetaPanel[0, :] r13 = ZetaPanel[3, :] - ZetaPanel[1, :] nvec = algebra.cross3(r02, r13) nvec = nvec / algebra.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 = algebra.norm3d(ra), algebra.norm3d(rb) vcross = algebra.cross3(ra, rb) vcross_sq = np.dot(vcross, vcross) # numerical radius if vcross_sq < (VORTEX_RADIUS_SQ * algebra.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 aero2struct_force_mapping(aero_forces, struct2aero_mapping, zeta, pos_def, psi_def, master, conn, cag=np.eye(3), aero_dict=None): r""" Maps the aerodynamic forces at the lattice to the structural nodes The aerodynamic forces from the UVLM are always in the inertial ``G`` frame of reference and have to be transformed to the body or local ``B`` frame of reference in which the structural forces are defined. Since the structural nodes and aerodynamic panels are coincident in a spanwise direction, the aerodynamic forces that correspond to a structural node are the summation of the ``M+1`` forces defined at the lattice at that spanwise location. .. math:: \mathbf{f}_{struct}^B &= \sum\limits_{i=0}^{m+1}C^{BG}\mathbf{f}_{i,aero}^G \\ \mathbf{m}_{struct}^B &= \sum\limits_{i=0}^{m+1}C^{BG}(\mathbf{m}_{i,aero}^G + \tilde{\boldsymbol{\zeta}}^G\mathbf{f}_{i, aero}^G) where :math:`\tilde{\boldsymbol{\zeta}}^G` is the skew-symmetric matrix of the vector between the lattice grid vertex and the structural node. Args: aero_forces (list): Aerodynamic forces from the UVLM in inertial frame of reference struct2aero_mapping (dict): Structural to aerodynamic node mapping zeta (list): Aerodynamic grid coordinates pos_def (np.ndarray): Vector of structural node displacements psi_def (np.ndarray): Vector of structural node rotations (CRVs) master: Unused conn (np.ndarray): Connectivities matrix cag (np.ndarray): Transformation matrix between inertial and body-attached reference ``A`` aero_dict (dict): Dictionary containing the grid's information. Returns: np.ndarray: structural forces in an ``n_node x 6`` vector """ n_node, _ = pos_def.shape n_elem, _, _ = psi_def.shape struct_forces = np.zeros((n_node, 6)) nodes = [] for i_elem in range(n_elem): for i_local_node in range(3): i_global_node = conn[i_elem, i_local_node] if i_global_node in nodes: continue nodes.append(i_global_node) for mapping in struct2aero_mapping[i_global_node]: i_surf = mapping['i_surf'] i_n = mapping['i_n'] _, n_m, _ = aero_forces[i_surf].shape crv = psi_def[i_elem, i_local_node, :] cab = algebra.crv2rotation(crv) cbg = np.dot(cab.T, cag) for i_m in range(n_m): chi_g = zeta[i_surf][:, i_m, i_n] - np.dot(cag.T, pos_def[i_global_node, :]) struct_forces[i_global_node, 0:3] += np.dot(cbg, aero_forces[i_surf][0:3, i_m, i_n]) struct_forces[i_global_node, 3:6] += np.dot(cbg, aero_forces[i_surf][3:6, i_m, i_n]) struct_forces[i_global_node, 3:6] += np.dot(cbg, algebra.cross3(chi_g, aero_forces[i_surf][0:3, i_m, i_n])) return struct_forces
def eval_seg_exp_loop(DerP, DerA, DerB, ZetaP, ZetaA, ZetaB, gamma_seg, vortex_radius): """ 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 = algebra.cross3(RA, RB) vcr2 = np.dot(Vcr, Vcr) # numerical radious vortex_radious_here = vortex_radius * algebra.norm3d(RAB) if vcr2 < vortex_radious_here**2: return # scaling ra1, rb1 = algebra.norm3d(RA), algebra.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. * algebra.cross3(RA, Vcr) rb_vcr_x, rb_vcr_y, rb_vcr_z = 2. * algebra.cross3(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, vortex_radius, 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. """ vortex_radius_sq = vortex_radius * vortex_radius 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 = [algebra.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 = algebra.cross3(R_list[aa], R_list[bb]) vcr2 = np.dot(Vcr, Vcr) if vcr2 < (vortex_radius_sq * algebra.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, vortex_radius, 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. """ vortex_radius_sq = vortex_radius * vortex_radius 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 = [algebra.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 = algebra.cross3(R_list[aa], R_list[bb]) vcr2 = np.dot(Vcr, Vcr) if vcr2 < (vortex_radius_sq * algebra.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