Source code for openaerostruct.aerodynamics.wave_drag

import numpy as np

import openmdao.api as om


[docs] class WaveDrag(om.ExplicitComponent): """ Compute the wave drag if the with_wave option is True. If not, the CDw is 0. This component exists for each lifting surface. Parameters ---------- Mach_number : float Mach number. widths[ny-1] : numpy array The width in the spanwise direction of each VLM panel. This is the numerator of cos(sweep). lengths_spanwise[ny-1] : numpy array The spanwise length of each VLM panel at 1/4 chord, rotated by the sweep angle. This is the denominator of cos(sweep) CL : float The CL of the lifting surface used for wave drag estimation. chords[ny] : numpy array The chord length of each mesh slice. This is dimension ny rather than ny-1 which would be expected for chord length of each VLM panel. t_over_c[ny-1] : numpy array The streamwise thickness-to-chord ratio of each VLM panel. Returns ------- CDw : float Wave drag coefficient for the lifting surface computed using equations based on the Korn equation """ def initialize(self): self.options.declare("surface", types=dict) self.options.declare("with_wave", types=bool) def setup(self): self.surface = surface = self.options["surface"] self.with_wave = surface["with_wave"] # Thickness over chord for the airfoil self.ka = 0.95 # airfoil technology level (for NASA SC airfoil) ny = surface["mesh"].shape[1] self.add_input("Mach_number", val=1.6, tags=["mphys_input"]) self.add_input("widths", val=np.ones((ny - 1)) * 0.2, units="m", tags=["mphys_coupling"]) self.add_input( "lengths_spanwise", val=np.arange((ny - 1)) + 1.0, units="m", tags=["mphys_coupling"] ) # set to np.arange so that d_CDw_d_chords is nonzero self.add_input("CL", val=0.33) self.add_input("chords", val=np.ones((ny)), units="m", tags=["mphys_coupling"]) self.add_input("t_over_c", val=np.arange((ny - 1)), tags=["mphys_input"]) self.add_output("CDw", val=0.0) self.declare_partials("CDw", "*") self.set_check_partial_options(wrt="*", method="cs", step=1e-50) def compute(self, inputs, outputs): if self.with_wave: t_over_c = inputs["t_over_c"] widths = inputs["widths"] cos_sweep = widths / inputs["lengths_spanwise"] M = inputs["Mach_number"] chords = inputs["chords"] CL = inputs["CL"] panel_mid_chords = (chords[:-1] + chords[1:]) / 2.0 panel_areas = panel_mid_chords * widths sum_panel_areas = np.sum(panel_areas) avg_cos_sweep = np.sum(cos_sweep * panel_areas) / sum_panel_areas # weighted average of 1/4 chord sweep avg_t_over_c = np.sum(t_over_c * panel_areas) / sum_panel_areas # weighted average of streamwise t/c MDD = self.ka / avg_cos_sweep - avg_t_over_c / avg_cos_sweep**2 - CL / (10 * avg_cos_sweep**3) Mcrit = MDD - (0.1 / 80.0) ** (1.0 / 3.0) if np.real(M) > np.real(Mcrit): outputs["CDw"] = 20 * (M - Mcrit) ** 4 else: outputs["CDw"] = 0.0 if self.surface["symmetry"]: outputs["CDw"] *= 2 else: outputs["CDw"] = 0.0 def compute_partials(self, inputs, partials): """Jacobian for wave drag.""" # Explicitly zero out the partials to begin, we can't assume the input partials arrays contain zeros already partials["CDw", "CL"][:] = 0.0 partials["CDw", "lengths_spanwise"][:] = 0.0 partials["CDw", "widths"][:] = 0.0 partials["CDw", "Mach_number"][:] = 0.0 partials["CDw", "chords"][:] = 0.0 partials["CDw", "t_over_c"][:] = 0.0 if self.with_wave: ny = self.surface["mesh"].shape[1] t_over_c = inputs["t_over_c"] widths = inputs["widths"] lengths_spanwise = inputs["lengths_spanwise"] cos_sweep = widths / lengths_spanwise M = inputs["Mach_number"] chords = inputs["chords"] CL = inputs["CL"] panel_mid_chords = (chords[:-1] + chords[1:]) / 2.0 panel_areas = panel_mid_chords * widths sum_panel_areas = np.sum(panel_areas) avg_cos_sweep = np.sum(cos_sweep * panel_areas) / sum_panel_areas avg_t_over_c = np.sum(t_over_c * panel_areas) / sum_panel_areas MDD = self.ka / avg_cos_sweep - avg_t_over_c / avg_cos_sweep**2 - CL / (10 * avg_cos_sweep**3) Mcrit = MDD - (0.1 / 80.0) ** (1.0 / 3.0) if np.real(M) > np.real(Mcrit): dCDwdMDD = -80 * (M - Mcrit) ** 3 dMDDdCL = -1.0 / (10 * avg_cos_sweep**3) dMDDdavg = (-10 * self.ka * avg_cos_sweep**2 + 20 * avg_t_over_c * avg_cos_sweep + 3 * CL) / ( 10 * avg_cos_sweep**4 ) dMDDdtoc = -1.0 / (avg_cos_sweep**2) dtocavgdtoc = panel_areas / sum_panel_areas ccos = np.sum(widths * panel_mid_chords) ccos2w = np.sum(panel_mid_chords * widths**2 / lengths_spanwise) davgdcos = ( 2 * panel_mid_chords * widths / lengths_spanwise / ccos - panel_mid_chords * ccos2w / ccos**2 ) dtocdcos = ( panel_mid_chords * t_over_c / ccos - panel_mid_chords * np.sum(panel_mid_chords * widths * t_over_c) / ccos**2 ) davgdw = -1 * panel_mid_chords * widths**2 / lengths_spanwise**2 / ccos davgdc = widths**2 / lengths_spanwise / ccos - widths * ccos2w / ccos**2 dtocdc = t_over_c * widths / ccos - widths * np.sum(panel_mid_chords * widths * t_over_c) / ccos**2 dcdchords = np.zeros((ny - 1, ny)) i, j = np.indices(dcdchords.shape) dcdchords[i == j] = 0.5 dcdchords[i == j - 1] = 0.5 partials["CDw", "Mach_number"] = -1 * dCDwdMDD partials["CDw", "CL"] = dCDwdMDD * dMDDdCL partials["CDw", "lengths_spanwise"] = dCDwdMDD * dMDDdavg * davgdw partials["CDw", "widths"] = dCDwdMDD * dMDDdavg * davgdcos + dCDwdMDD * dMDDdtoc * dtocdcos partials["CDw", "chords"] = dCDwdMDD * dMDDdavg * np.matmul( davgdc, dcdchords ) + dCDwdMDD * dMDDdtoc * np.matmul(dtocdc, dcdchords) partials["CDw", "t_over_c"] = dCDwdMDD * dMDDdtoc * dtocavgdtoc if self.surface["symmetry"]: partials["CDw", "CL"][0, :] *= 2 partials["CDw", "lengths_spanwise"][0, :] *= 2 partials["CDw", "widths"][0, :] *= 2 partials["CDw", "Mach_number"][0, :] *= 2 partials["CDw", "chords"][0, :] *= 2 partials["CDw", "t_over_c"][0, :] *= 2