[docs]classStructureWeightLoads(om.ExplicitComponent):""" Compute the nodal loads from the weight of the wing structure to be applied to the wing structure. Parameters ---------- element_mass[ny-1] : numpy array Weight for each wing-structure segment. nodes[ny, 3] : numpy array Flattened array with coordinates for each FEM node. load_factor : float Load factor for the flight point. Returns ------- struct_weight_loads[ny, 6] : numpy array Flattened array containing the loads applied on the FEM component, computed from the weight of the wing-structure segments. element_lengths[ny-1] : numpy array Lengths of the FEM finite elements. """definitialize(self):self.options.declare("surface",types=dict)defsetup(self):self.surface=surface=self.options["surface"]self.ny=surface["mesh"].shape[1]self.add_input("element_mass",val=np.zeros((self.ny-1)),units="kg")self.add_input("nodes",val=np.zeros((self.ny,3),dtype=complex),units="m")self.add_input("load_factor",val=1.0)self.add_output("struct_weight_loads",val=np.zeros((self.ny,6)),units="N")self.add_output("element_lengths",val=np.zeros(self.ny-1),units="m")nym1=self.ny-1# dloads__dzfrows=np.zeros(2*nym1)cols=np.zeros(2*nym1)data=-np.ones(2*nym1)counter=0foriinrange(nym1):forjinrange(2):rows[counter]=6*(i+j)+2cols[counter]=icounter+=1self.dswl__dzf=coo_matrix((data,(rows,cols)),shape=(self.ny*6,nym1))rows=np.zeros(3*self.ny)cols=np.zeros(3*self.ny)forjinrange(3):foriinrange(self.ny):idx=i+self.ny*jrows[idx]=6*i+j+2self.dswl__dlf_row=rowsself.dswl__dlf_col=colsself.declare_partials("struct_weight_loads","load_factor",rows=rows,cols=cols)# self.declare_partials('struct_weight_loads', 'load_factor')# dstruct_weight_loads__dnodes (this one is super complicated)# we can pre-compute several of the intermediate partial derivative matrices# others we can pre-compute the rows/cols so we just have to set the data# the for loops for rows/cols are a bit expensive (cause python), but the data computations are fast# so we will pay the one-time setup cost to pre-compute the rows/colsrows_del=np.zeros(2*nym1)cols_d0=np.zeros(2*nym1)cols_d1=np.zeros(2*nym1)data=np.zeros(2*nym1)counter=0foriinrange(nym1):forjinrange(2):rows_del[counter]=icols_d0[counter]=3*(i+j)cols_d1[counter]=3*(i+j)+1data[counter]=2*j-1# this gives 1 or -1 alternating based on jcounter+=1shape=(nym1,3*self.ny)self.ddel0__dnodes=coo_matrix((data,(rows_del,cols_d0)),shape=shape)self.ddel1__dnodes=coo_matrix((data,(rows_del,cols_d1)),shape=shape)# del__dnodesrows_el=np.zeros(6*nym1)cols_el=np.zeros(6*nym1)data=np.ones(6*nym1)# del__dnodes matrix has only ones in itcounter=0foriinrange(nym1):forjinrange(6):rows_el[counter]=icols_el[counter]=3*i+jcounter+=1self.del__dnodes=coo_matrix((data,(rows_el,cols_el)),shape=shape)rng=np.random.default_rng(0)nym1_rand=rng.random(nym1)dzm_dnodes_pattern=((diags(nym1_rand*nym1_rand)*self.ddel0__dnodes+diags(nym1_rand*nym1_rand)*self.ddel1__dnodes)).tocsr()dbm3_dnodes=(diags(nym1_rand)*dzm_dnodes_pattern+diags(nym1_rand)*self.ddel1__dnodes-diags(nym1_rand)*self.del__dnodes)dbm4_dnodes=(diags(nym1_rand)*dzm_dnodes_pattern+diags(nym1_rand)*self.ddel0__dnodes-diags(nym1_rand)*self.del__dnodes)self.dbm3_dnodes_pattern=(diags(nym1_rand)*dzm_dnodes_pattern+diags(nym1_rand)*self.ddel1__dnodes-diags(nym1_rand)*self.del__dnodes)self.dbm4_dnodes_pattern=(diags(nym1_rand)*dzm_dnodes_pattern+diags(nym1_rand)*self.ddel0__dnodes-diags(nym1_rand)*self.del__dnodes)# dswl__dbmrows_1=np.zeros(2*nym1)rows_0=np.zeros(2*nym1)cols=np.zeros(2*nym1)data=rng.random(2*nym1)counter=0foriinrange(nym1):forjinrange(2):rows_1[counter]=6*(i+j)+3rows_0[counter]=6*(i+j)+4ifcounter%2==0:# evendata[counter]=1else:# odddata[counter]=-1cols[counter]=icounter+=1self.dswl__dbm3=coo_matrix((data,(rows_1,cols)),shape=(6*self.ny,nym1)).tocsr()self.dswl__dbm4=coo_matrix((data,(rows_0,cols)),shape=(6*self.ny,nym1)).tocsr()self.dswl__dnodes_pattern=(self.dswl__dbm3*dbm3_dnodes+self.dswl__dbm4*dbm4_dnodes).tocoo()self.dswl__dew_pattern=(self.dswl__dbm4+self.dswl__dbm3+self.dswl__dzf).tocoo()self.declare_partials("struct_weight_loads","nodes",rows=self.dswl__dnodes_pattern.row,cols=self.dswl__dnodes_pattern.col)self.declare_partials("struct_weight_loads","element_mass",rows=self.dswl__dew_pattern.row,cols=self.dswl__dew_pattern.col)self.set_check_partial_options(wrt="*",method="cs")defcompute(self,inputs,outputs):struct_weights=inputs["element_mass"]*inputs["load_factor"]*grav_constantnodes=inputs["nodes"]element_lengths=norm(nodes[1:,:]-nodes[:-1,:],axis=1)# And we also need the deltas between consecutive nodesdeltas=nodes[1:,:]-nodes[:-1,:]# save these slices cause I use them a lotdel0=deltas[:,0]del1=deltas[:,1]# Assume weight coincides with the elastic axisz_forces_for_each=struct_weights/2.0z_moments_for_each=struct_weights/12.0*(del0**2+del1**2)**0.5loads=outputs["struct_weight_loads"]loads*=0# need to zero it out, since we're accumulating onto it# Why doesn't this trigger when running ../../tests/test_aerostruct_wingbox_+weight_analysis.py???ifself.under_complex_step:loads=np.zeros((self.ny,6),dtype=complex)# Loads in z-directionloads[:-1,2]+=-z_forces_for_eachloads[1:,2]+=-z_forces_for_each# Bending moments for consistencybm3=z_moments_for_each*del1/element_lengthsloads[:-1,3]+=-bm3loads[1:,3]+=bm3bm4=z_moments_for_each*del0/element_lengthsloads[:-1,4]+=-bm4loads[1:,4]+=bm4outputs["struct_weight_loads"]=loadsdefcompute_partials(self,inputs,J):struct_weights=inputs["element_mass"]*inputs["load_factor"]*grav_constantnodes=inputs["nodes"]element_lengths=norm(nodes[1:,:]-nodes[:-1,:],axis=1)# And we also need the deltas between consecutive nodesdeltas=nodes[1:,:]-nodes[:-1,:]# save these slices cause I use them alotdel0=deltas[:,0]del1=deltas[:,1]# Assume weight coincides with the elastic axisz_moments_for_each=struct_weights/12.0*(del0**2+del1**2)**0.5dzf__dew=0.5*inputs["load_factor"][0]dzf__dlf=inputs["element_mass"]/2.0dzm__dew=diags((del0**2+del1**2)**0.5/12*inputs["load_factor"])dzm__dlf=(del0**2+del1**2)**0.5/12.0*inputs["element_mass"]dbm3__dzm=diags(del1/element_lengths)dbm4__dzm=diags(del0/element_lengths)# need to convert to lil to re-order the data to match the original row/col indexing from setupdswl__dlf=(-self.dswl__dbm3*dbm3__dzm*coo_matrix(dzm__dlf).T+-self.dswl__dbm4*dbm4__dzm*coo_matrix(dzm__dlf).T+self.dswl__dzf*coo_matrix(dzf__dlf).T).tolil()J["struct_weight_loads","load_factor"]=(dswl__dlf[self.dswl__dlf_row,self.dswl__dlf_col].toarray().flatten()*grav_constant)dswl__dew=(-self.dswl__dbm4*dbm4__dzm*dzm__dew+-self.dswl__dbm3*dbm3__dzm*dzm__dew+self.dswl__dzf*dzf__dew).tolil()data=dswl__dew[self.dswl__dew_pattern.row,self.dswl__dew_pattern.col].toarray().flatten()J["struct_weight_loads","element_mass"]=data*grav_constant# dstruct_weight_loads__dnodes (this one is super complicated)# del__dnodes# note: del__dnodes matrix already created in setup, just need to set dataraw_data=diags(1/element_lengths)*(nodes[1:,:]-nodes[:-1,:])data=np.hstack((-raw_data,raw_data)).flatten()self.del__dnodes.data=datadzm_dnodes=diags(struct_weights/12*(del0**2+del1**2)**-0.5)*(diags(del0)*self.ddel0__dnodes+diags(del1)*self.ddel1__dnodes)dbm3_dnodes=(diags(del1/element_lengths)*dzm_dnodes+diags(z_moments_for_each/element_lengths)*self.ddel1__dnodes-diags(z_moments_for_each*del1/element_lengths**2)*self.del__dnodes)dbm4_dnodes=(diags(del0/element_lengths)*dzm_dnodes+diags(z_moments_for_each/element_lengths)*self.ddel0__dnodes-diags(z_moments_for_each*del0/element_lengths**2)*self.del__dnodes)# this is kind of dumb, but I need lil cause I have to re-index to preserve order# the coo column ordering doesn't seem to be deterministic# so I use the original row/col from the pattern as index arrays to# pull the data out in the correct orderdswl__dnodes=(self.dswl__dbm3*dbm3_dnodes+self.dswl__dbm4*dbm4_dnodes).tolil()data=dswl__dnodes[self.dswl__dnodes_pattern.row,self.dswl__dnodes_pattern.col].toarray().flatten()J["struct_weight_loads","nodes"]=-data