def test_get_PcOvPe(self): """ test call to get_PcOvPe( Pc=100.0, MR=1.0, eps=40.0) """ C = CEA_Obj(oxName="LOX", fuelName="MMH", fac_CR=None) PcOvPe = C.get_PcOvPe(Pc=100.0, MR=1.0, eps=40.0) self.assertAlmostEqual(PcOvPe, 550.3111877518063, places=3) del C
def test_get_PcOvPe(self): """ test call to get_PcOvPe( Pc=100.0, MR=1.0, eps=40.0) """ C = CEA_Obj(oxName="LOX", fuelName="MMH", fac_CR=2.5) PcOvPe = C.get_PcOvPe(Pc=100.0, MR=1.0, eps=40.0) self.assertAlmostEqual(PcOvPe, 568.3567593758571, places=3) del C
def test_froz_get_PcOvPe(self): """ test call to get_PcOvPe( Pc=100.0, MR=1.0, eps=40.0) """ C = CEA_Obj(oxName="LOX", fuelName="MMH", fac_CR=None) PcOvPe = C.get_PcOvPe(Pc=100.0, MR=1.0, eps=40.0, frozen=1, frozenAtThroat=0) self.assertAlmostEqual(PcOvPe, 670.4054686991491, places=3) del C
class CEA_Obj(object): """ RocketCEA wraps the NASA FORTRAN CEA code to calculate Isp, cstar, and Tcomb This object wraps the English unit version of CEA_Obj to enable desired user units. """ def __init__(self, propName='', oxName='', fuelName='', useFastLookup=0, makeOutput=0, isp_units='sec', cstar_units='ft/sec', pressure_units='psia', temperature_units='degR', sonic_velocity_units='ft/sec', enthalpy_units='BTU/lbm', density_units='lbm/cuft', specific_heat_units='BTU/lbm degR', viscosity_units='millipoise', thermal_cond_units='mcal/cm-K-s', fac_CR=None, make_debug_prints=False): """:: #: RocketCEA wraps the NASA FORTRAN CEA code to calculate Isp, cstar, and Tcomb #: This object wraps the English unit version of CEA_Obj to enable desired user units. #: Same as CEA_Obj with standard units except, input and output units can be specified. #: parameter default options #: isp_units = 'sec', # N-s/kg, m/s, km/s #: cstar_units = 'ft/sec', # m/s #: pressure_units = 'psia', # MPa, KPa, Pa, Bar, Atm, Torr #: temperature_units = 'degR', # K, C, F #: sonic_velocity_units = 'ft/sec', # m/s #: enthalpy_units = 'BTU/lbm', # J/g, kJ/kg, J/kg, kcal/kg, cal/g #: density_units = 'lbm/cuft', # g/cc, sg, kg/m^3 #: specific_heat_units = 'BTU/lbm degR' # kJ/kg-K, cal/g-C, J/kg-K (# note: cal/g K == BTU/lbm degR) #: viscosity_units = 'millipoise' # lbf-sec/sqin, lbf-sec/sqft, lbm/ft-sec, poise, centipoise #: thermal_cond_units = 'mcal/cm-K-s' # millical/cm-degK-sec, BTU/hr-ft-degF, BTU/s-in-degF, cal/s-cm-degC, W/cm-degC #: fac_CR, Contraction Ratio of finite area combustor (None=infinite) #: if make_debug_prints is True, print debugging info to terminal. """ self.isp_units = isp_units self.cstar_units = cstar_units self.pressure_units = pressure_units self.temperature_units = temperature_units self.sonic_velocity_units = sonic_velocity_units self.enthalpy_units = enthalpy_units self.density_units = density_units self.specific_heat_units = specific_heat_units self.viscosity_units = viscosity_units self.thermal_cond_units = thermal_cond_units self.fac_CR = fac_CR # Units objects for input/output (e.g. Pc and Pamb) self.Pc_U = get_units_obj('psia', pressure_units) # units of output quantities self.isp_U = get_units_obj('sec', isp_units) self.cstar_U = get_units_obj('ft/sec', cstar_units) self.temperature_U = get_units_obj('degR', temperature_units) self.sonic_velocity_U = get_units_obj('ft/sec', sonic_velocity_units) self.enthalpy_U = get_units_obj('BTU/lbm', enthalpy_units) self.density_U = get_units_obj('lbm/cuft', density_units) self.specific_heat_U = get_units_obj('BTU/lbm degR', specific_heat_units) self.viscosity_U = get_units_obj('millipoise', viscosity_units) self.thermal_cond_U = get_units_obj('mcal/cm-K-s', thermal_cond_units) self.cea_obj = CEA_Obj_default(propName=propName, oxName=oxName, fuelName=fuelName, useFastLookup=useFastLookup, makeOutput=makeOutput, fac_CR=fac_CR, make_debug_prints=make_debug_prints) self.desc = self.cea_obj.desc def get_IvacCstrTc(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia IspVac, Cstar, Tcomb = self.cea_obj.get_IvacCstrTc(Pc=Pc, MR=MR, eps=eps) IspVac = self.isp_U.dval_to_uval(IspVac) Cstar = self.cstar_U.dval_to_uval(Cstar) Tcomb = self.temperature_U.dval_to_uval(Tcomb) return IspVac, Cstar, Tcomb def getFrozen_IvacCstrTc(self, Pc=100.0, MR=1.0, eps=40.0, frozenAtThroat=0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia IspFrozen, Cstar, Tcomb = self.cea_obj.getFrozen_IvacCstrTc( Pc=Pc, MR=MR, eps=eps, frozenAtThroat=frozenAtThroat) IspFrozen = self.isp_U.dval_to_uval(IspFrozen) Cstar = self.cstar_U.dval_to_uval(Cstar) Tcomb = self.temperature_U.dval_to_uval(Tcomb) return IspFrozen, Cstar, Tcomb def get_IvacCstrTc_exitMwGam(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia IspVac, Cstar, Tcomb, mw, gam = self.cea_obj.get_IvacCstrTc_exitMwGam( Pc=Pc, MR=MR, eps=eps) IspVac = self.isp_U.dval_to_uval(IspVac) Cstar = self.cstar_U.dval_to_uval(Cstar) Tcomb = self.temperature_U.dval_to_uval(Tcomb) return IspVac, Cstar, Tcomb, mw, gam def get_IvacCstrTc_ChmMwGam(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia IspVac, Cstar, Tcomb, mw, gam = self.cea_obj.get_IvacCstrTc_ChmMwGam( Pc=Pc, MR=MR, eps=eps) IspVac = self.isp_U.dval_to_uval(IspVac) Cstar = self.cstar_U.dval_to_uval(Cstar) Tcomb = self.temperature_U.dval_to_uval(Tcomb) return IspVac, Cstar, Tcomb, mw, gam def get_IvacCstrTc_ThtMwGam(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia IspVac, Cstar, Tcomb, mw, gam = self.cea_obj.get_IvacCstrTc_ThtMwGam( Pc=Pc, MR=MR, eps=eps) IspVac = self.isp_U.dval_to_uval(IspVac) Cstar = self.cstar_U.dval_to_uval(Cstar) Tcomb = self.temperature_U.dval_to_uval(Tcomb) return IspVac, Cstar, Tcomb, mw, gam def __call__(self, Pc=100.0, MR=1.0, eps=40.0): return self.get_Isp(Pc=Pc, MR=MR, eps=eps) def get_Isp(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia IspVac = self.cea_obj.get_Isp(Pc=Pc, MR=MR, eps=eps) IspVac = self.isp_U.dval_to_uval(IspVac) return IspVac def get_Cstar(self, Pc=100.0, MR=1.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Cstar = self.cea_obj.get_Cstar(Pc=Pc, MR=MR) Cstar = self.cstar_U.dval_to_uval(Cstar) return Cstar def get_Tcomb(self, Pc=100.0, MR=1.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Tcomb = self.cea_obj.get_Tcomb(Pc=Pc, MR=MR) Tcomb = self.temperature_U.dval_to_uval(Tcomb) return Tcomb def get_PcOvPe(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia return self.cea_obj.get_PcOvPe(Pc=Pc, MR=MR, eps=eps) def get_eps_at_PcOvPe(self, Pc=100.0, MR=1.0, PcOvPe=1000.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia return self.cea_obj.get_eps_at_PcOvPe(Pc=Pc, MR=MR, PcOvPe=PcOvPe) def get_Throat_PcOvPe(self, Pc=100.0, MR=1.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia return self.cea_obj.get_Throat_PcOvPe(Pc=Pc, MR=MR) def get_MachNumber(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia return self.cea_obj.get_MachNumber(Pc=Pc, MR=MR, eps=eps) def get_Temperatures(self, Pc=100.0, MR=1.0, eps=40.0, frozen=0, frozenAtThroat=0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia tempList = self.cea_obj.get_Temperatures(Pc=Pc, MR=MR, eps=eps, frozen=frozen, frozenAtThroat=frozenAtThroat) for i, T in enumerate(tempList): tempList[i] = self.temperature_U.dval_to_uval(T) return tempList # Tc, Tthroat, Texit def get_SonicVelocities(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia sonicList = self.cea_obj.get_SonicVelocities(Pc=Pc, MR=MR, eps=eps) for i, S in enumerate(sonicList): sonicList[i] = self.sonic_velocity_U.dval_to_uval(S) return sonicList # Chamber, Throat, Exit def get_Chamber_SonicVel(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia sonicVel = self.cea_obj.get_Chamber_SonicVel(Pc=Pc, MR=MR, eps=eps) sonicVel = self.sonic_velocity_U.dval_to_uval(sonicVel) return sonicVel def get_Enthalpies(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia hList = self.cea_obj.get_Enthalpies(Pc=Pc, MR=MR, eps=eps) for i, H in enumerate(hList): hList[i] = self.enthalpy_U.dval_to_uval(H) return hList def get_SpeciesMassFractions(self, Pc=100.0, MR=1.0, eps=40.0, frozen=0, frozenAtThroat=0, min_fraction=0.000005): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia molWtD, massFracD = self.cea_obj.get_SpeciesMassFractions( Pc=Pc, MR=MR, eps=eps, frozenAtThroat=frozenAtThroat, min_fraction=min_fraction) return molWtD, massFracD def get_SpeciesMoleFractions(self, Pc=100.0, MR=1.0, eps=40.0, frozen=0, frozenAtThroat=0, min_fraction=0.000005): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia molWtD, moleFracD = self.cea_obj.get_SpeciesMoleFractions( Pc=Pc, MR=MR, eps=eps, frozenAtThroat=frozenAtThroat, min_fraction=min_fraction) return molWtD, moleFracD def get_Chamber_H(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia H = self.cea_obj.get_Chamber_H(Pc=Pc, MR=MR, eps=eps) return self.enthalpy_U.dval_to_uval(H) def get_Densities(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia dList = self.cea_obj.get_Densities(Pc=Pc, MR=MR, eps=eps) for i, d in enumerate(dList): dList[i] = self.density_U.dval_to_uval(d) return dList def get_Chamber_Density(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia H = self.cea_obj.get_Chamber_Density(Pc=Pc, MR=MR, eps=eps) return self.density_U.dval_to_uval(H) def get_HeatCapacities(self, Pc=100.0, MR=1.0, eps=40.0, frozen=0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia cpList = self.cea_obj.get_HeatCapacities(Pc=Pc, MR=MR, eps=eps, frozen=frozen) for i, cp in enumerate(cpList): cpList[i] = self.specific_heat_U.dval_to_uval(cp) return cpList def get_Chamber_Cp(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Cp = self.cea_obj.get_Chamber_Cp(Pc=Pc, MR=MR, eps=eps) return self.specific_heat_U.dval_to_uval(Cp) def get_Throat_Isp(self, Pc=100.0, MR=1.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Isp = self.cea_obj.get_Throat_Isp(Pc=Pc, MR=MR) Isp = self.isp_U.dval_to_uval(Isp) return Isp def get_Chamber_MolWt_gamma(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia return self.cea_obj.get_Chamber_MolWt_gamma(Pc=Pc, MR=MR, eps=eps) def get_Throat_MolWt_gamma(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia return self.cea_obj.get_Throat_MolWt_gamma(Pc=Pc, MR=MR, eps=eps) def get_exit_MolWt_gamma(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia return self.cea_obj.get_exit_MolWt_gamma(Pc=Pc, MR=MR, eps=eps) def get_eqratio(self, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia return self.cea_obj.get_eqratio(Pc=Pc, MR=MR, eps=eps) def getMRforER(self, ERphi=None, ERr=None): return self.cea_obj.getMRforER(ERphi=ERphi, ERr=ERr) def get_description(self): return self.cea_obj.get_description() def estimate_Ambient_Isp(self, Pc=100.0, MR=1.0, eps=40.0, Pamb=14.7, frozen=0, frozenAtThroat=0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Pamb = self.Pc_U.uval_to_dval(Pamb) # convert user units to psia IspAmb, mode = self.cea_obj.estimate_Ambient_Isp( Pc=Pc, MR=MR, eps=eps, Pamb=Pamb, frozen=frozen, frozenAtThroat=frozenAtThroat) IspAmb = self.isp_U.dval_to_uval(IspAmb) return IspAmb, mode def get_PambCf(self, Pamb=14.7, Pc=100.0, MR=1.0, eps=40.0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Pamb = self.Pc_U.uval_to_dval(Pamb) # convert user units to psia CFcea, CF, mode = self.cea_obj.get_PambCf(Pamb=Pamb, Pc=Pc, MR=MR, eps=eps) return CFcea, CF, mode def getFrozen_PambCf(self, Pamb=0.0, Pc=100.0, MR=1.0, eps=40.0, frozenAtThroat=0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Pamb = self.Pc_U.uval_to_dval(Pamb) # convert user units to psia CFcea, CFfrozen, mode = self.cea_obj.getFrozen_PambCf( Pamb=Pamb, Pc=Pc, MR=MR, eps=eps, frozenAtThroat=frozenAtThroat) return CFcea, CFfrozen, mode def get_Chamber_Transport(self, Pc=100.0, MR=1.0, eps=40.0, frozen=0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Cp, visc, cond, Prandtl = self.cea_obj.get_Chamber_Transport( Pc=Pc, MR=MR, eps=eps, frozen=frozen) #Cp = Cp * 8314.51 / 4184.0 # convert into BTU/lbm degR Cp = self.specific_heat_U.dval_to_uval(Cp) visc = self.viscosity_U.dval_to_uval(visc) cond = self.thermal_cond_U.dval_to_uval(cond) return Cp, visc, cond, Prandtl def get_Throat_Transport(self, Pc=100.0, MR=1.0, eps=40.0, frozen=0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Cp, visc, cond, Prandtl = self.cea_obj.get_Throat_Transport( Pc=Pc, MR=MR, eps=eps, frozen=frozen) #Cp = Cp * 8314.51 / 4184.0 # convert into BTU/lbm degR Cp = self.specific_heat_U.dval_to_uval(Cp) visc = self.viscosity_U.dval_to_uval(visc) cond = self.thermal_cond_U.dval_to_uval(cond) return Cp, visc, cond, Prandtl def get_Exit_Transport(self, Pc=100.0, MR=1.0, eps=40.0, frozen=0): Pc = self.Pc_U.uval_to_dval(Pc) # convert user units to psia Cp, visc, cond, Prandtl = self.cea_obj.get_Exit_Transport( Pc=Pc, MR=MR, eps=eps, frozen=frozen) Cp = Cp * 8314.51 / 4184.0 # convert into BTU/lbm degR Cp = self.specific_heat_U.dval_to_uval(Cp) visc = self.viscosity_U.dval_to_uval(visc) cond = self.thermal_cond_U.dval_to_uval(cond) return Cp, visc, cond, Prandtl
R_specific_comb_in = (R / (12 * c_molweight) ) #Universal Gas Constant for Chamber #Nozzle Parameters #Throat t_area = (mass_flow / pcc) * sqrt( (c_temp * R_specific_comb_in) / c_gamma) * (1 + (((c_gamma - 1) / 2)**( (c_gamma + 1) / (2 * (c_gamma - 1))))) #Throat Area (in^2) - refer: https://www.grc.nasa.gov/www/k-12/airplane/rktthsum.html #Assuming Chamber Temperature and Chamber Pressure are Stagnation Pressures - velocity in CC assumed to be negligible t_radius = sqrt(t_area / pi) #Throat Radius (in) t_diameter = 2 * 2.54 * t_radius #Throat Diameter (cm) t_area_cm = t_area * 6.4516 #Throat Area (cm^2) #Exit e_pressureratio = ispObj.get_PcOvPe( pcc, mr, eps) #Ratio of Pressure from Combustion Chamber to Exit e_pressure_actual = pcc / e_pressureratio #Actual Exit Pressure (psi) e_area = eps * t_area #Nozzle Exit Area (in^2) e_prop = ispObj.get_IvacCstrTc_exitMwGam(pcc, mr, eps) #Nozzle Exit Properties e_molweight = e_prop[3] #Molecular Weight - in lb/lbmol e_gamma = e_prop[ 4] #Nozzle Exit Gamma - this parameter changes significantly from the chamber gamma e_mach = ispObj.get_MachNumber(pcc, mr, eps) #Exit Mach Number #Augmented Constants R_specific_exit = (R / e_molweight) #Universal Gas Constant for Nozzle Exit #Thrust Calcs e_vel_HH = sqrt( ((2 * g * e_gamma * R_specific_exit * c_temp) / (e_gamma - 1)) * (1 - ((pamb) / (pcc))**((e_gamma - 1) / (e_gamma)))) #H&H Eqn 1.18
class CoreStream: """ Core stream tube of liquid bipropellant thruster. :param geomObj: Geometry that describes thruster :param effObj: Efficiencies object to hold individual efficiencies :param oxName: name of oxidizer (e.g. N2O4, LOX) :param fuelName: name of fuel (e.g. MMH, LH2) :param MRcore: mixture ratio of core flow (ox flow rate / fuel flow rate) :param Pc: psia, chamber pressure :param CdThroat: Cd of throat (RocketThruster object may override) :param Pamb: psia, ambient pressure (for example sea level is 14.7 psia) :param adjCstarODE: multiplier on NASA CEA code value of cstar ODE (default is 1.0) :param adjIspIdeal: multiplier on NASA CEA code value of Isp ODE (default is 1.0) :param pcentFFC: percent fuel film cooling (if > 0 then add BarrierStream) :param ko: entrainment constant (passed to BarrierStream object, range from 0.03 to 0.06) :param ignore_noz_sep: flag to force nozzle flow separation to be ignored (USE WITH CAUTION) :type geomObj: Geometry :type effObj: Efficiencies :type oxName: str :type fuelName: str :type MRcore: float :type Pc: float :type CdThroat: float :type Pamb: float :type adjCstarODE: float :type adjIspIdeal: float :type pcentFFC: float :type ko: float :type ignore_noz_sep: bool :return: CoreStream object :rtype: CoreStream :ivar FvacTotal: lbf, total vacuum thrust :ivar FvacCore: lbf, vacuum thrust due to core stream tube :ivar MRthruster: total thruster mixture ratio') :ivar IspDel: sec, <=== thruster delivered vacuum Isp ===> :ivar Pexit: psia, nozzle exit pressure :ivar IspDel_core: sec, delivered Isp of core stream tube :ivar IspODF: sec, core frozen Isp :ivar IspODK: sec, core one dimensional kinetic Isp :ivar IspODE: sec, core one dimensional equilibrium Isp :ivar cstarERE: ft/s, delivered core cstar :ivar cstarODE: ft/s, core ideal cstar :ivar CfVacIdeal: ideal vacuum thrust coefficient :ivar CfVacDel: delivered vacuum thrust coefficient :ivar CfAmbDel: delivered ambient thrust coefficient :ivar wdotTot: lbm/s, total propellant flow rate (ox+fuel) :ivar wdotOx: lbm/s, total oxidizer flow rate :ivar wdotFl: lbm/s, total fuel flow rate :ivar TcODE: degR, ideal core gas temperature :ivar MWchm: g/gmole, core gas molecular weight :ivar gammaChm: core gas ratio of specific heats (Cp/Cv) """ def __init__( self, geomObj=Geometry(), effObj=Efficiencies(), #ERE=0.98, Noz=0.97), oxName='N2O4', fuelName='MMH', MRcore=1.9, Pc=500, CdThroat=0.995, Pamb=0.0, adjCstarODE=1.0, adjIspIdeal=1.0, pcentFFC=0.0, ko=0.035, ignore_noz_sep=False): self.geomObj = geomObj self.effObj = effObj self.oxName = oxName self.fuelName = fuelName self.MRcore = MRcore self.Pc = Pc self.Pamb = Pamb # ambient pressure self.noz_mode = '' self.CdThroat = CdThroat self.CdThroat_method = 'default' self.ignore_noz_sep = ignore_noz_sep # ignore any nozzle separation self.adjCstarODE = adjCstarODE # may want to adjust ODE cstar value self.adjIspIdeal = adjIspIdeal # may want to adjust ODE and ODF Isp values # make CEA object self.ceaObj = CEA_Obj(oxName=oxName, fuelName=fuelName) # ... if pcentFFC > 0.0, then there's barrier cooling if pcentFFC > 0.0: self.add_barrier = True else: self.add_barrier = False # barrier might need some performance parameters from CoreStream self.calc_cea_perf_params() if self.add_barrier: self.barrierObj = BarrierStream(self, pcentFFC=pcentFFC, ko=ko) else: self.barrierObj = None self.evaluate() # get input descriptions and units from doc string self.inp_descD, self.inp_unitsD, self.is_inputD = get_desc_and_units( self.__doc__) def __call__(self, name): return getattr(self, name) # let it raise exception if no name attr. def reset_CdThroat(self, value, method_name='RocketIsp', re_evaluate=True): """ reset the value of CdThroat If re_evaluate is True, then call self.evaluate() after resetting the efficiency. """ self.CdThroat = value self.CdThroat_method = method_name if re_evaluate: self.evaluate() def reset_attr(self, name, value, re_evaluate=True): """ reset the value of any existing attribute of CoreStream instance. If re_evaluate is True, then call self.evaluate() after resetting the value of the attribute. """ if hasattr(self, name): setattr(self, name, value) else: raise Exception( 'Attempting to set un-authorized CoreStream attribute named "%s"' % name) if name in ['oxName', 'fuelName']: # make CEA object self.ceaObj = CEA_Obj(oxName=self.oxName, fuelName=self.fuelName) if re_evaluate: self.evaluate() def calc_cea_perf_params(self): """Calc basic Isp values from CEA and calc implied IspODK from current effKin value.""" # calc ideal CEA performance parameters self.IspODE, self.cstarODE, self.TcODE, self.MWchm, self.gammaChm = \ self.ceaObj.get_IvacCstrTc_ChmMwGam( Pc=self.Pc, MR=self.MRcore, eps=self.geomObj.eps) self.cstarODE *= self.adjCstarODE self.IspODE *= self.adjIspIdeal self.IspODF, _, _ = self.ceaObj.getFrozen_IvacCstrTc( Pc=self.Pc, MR=self.MRcore, eps=self.geomObj.eps) self.IspODF *= self.adjIspIdeal # use user effKin to set IspODK (or most recent update) self.IspODK = self.IspODE * self.effObj('Kin') #self.IspODK = calc_IspODK(self.ceaObj, Pc=self.Pc, eps=self.geomObj.eps, # Rthrt=self.geomObj.Rthrt, # pcentBell=self.geomObj.pcentBell, # MR=self.MRcore) # fraction of equilibrium kinetics obtained self.fracKin = (self.IspODK - self.IspODF) / (self.IspODE - self.IspODF) self.Pexit = self.Pc / self.ceaObj.get_PcOvPe( Pc=self.Pc, MR=self.MRcore, eps=self.geomObj.eps) self.CfVacIdeal = 32.174 * self.IspODE / self.cstarODE def evaluate(self): """ Assume that all efficiencies have been set, either by original user value or an update by an efficiency model. """ self.effObj.evaluate() self.calc_cea_perf_params() # make final summary efficiencies effNoz = self.effObj('Noz') # want a Core-Only ERE in case a barrier calc is done effERE_core = self.effObj('ERE') if not self.add_barrier: # if no barrier, user may have input FFC effERE_core = effERE_core * self.effObj('FFC') cstarERE_core = self.cstarODE * effERE_core effIsp_core = effNoz * effERE_core self.IspDel_core = effIsp_core * self.IspODE if self.add_barrier: self.barrierObj.evaluate() fAtc = solve_At_split(self.MRcore, self.barrierObj.MRbarrier, self.barrierObj.pcentFFC / 100.0, cstarERE_core, self.barrierObj.cstarERE_b) self.frac_At_core = fAtc # core shares throat area with barrier stream self.frac_At_barrier = 1.0 - self.frac_At_core self.At_b = self.frac_At_barrier * self.geomObj.At self.wdotTot_b = self.Pc * self.At_b * self.CdThroat * 32.174 / self.barrierObj.cstarERE_b self.wdotOx_b = self.wdotTot_b * self.barrierObj.MRbarrier / ( 1.0 + self.barrierObj.MRbarrier) self.wdotFl_b = self.wdotTot_b - self.wdotOx_b self.FvacBarrier = self.wdotTot_b * self.barrierObj.IspDel_b self.MRthruster = self.MRcore * (1.0 - self.barrierObj.pcentFFC / 100.0) else: self.frac_At_core = 1.0 # core gets all of throat area if no barrier stream self.frac_At_barrier = 0.0 self.FvacBarrier = 0.0 self.MRthruster = self.MRcore self.wdotTot_b = 0.0 self.wdotOx_b = 0.0 self.wdotFl_b = 0.0 self.Atcore = self.frac_At_core * self.geomObj.At self.wdotTot_c = self.Pc * self.Atcore * self.CdThroat * 32.174 / cstarERE_core self.wdotOx_c = self.wdotTot_c * self.MRcore / (1.0 + self.MRcore) self.wdotFl_c = self.wdotTot_c - self.wdotOx_c self.FvacCore = self.wdotTot_c * self.IspDel_core self.FvacTotal = self.FvacCore + self.FvacBarrier self.wdotTot = self.wdotTot_c + self.wdotTot_b self.wdotOx = self.wdotOx_c + self.wdotOx_b self.wdotFl = self.wdotFl_c + self.wdotFl_b if self.add_barrier: self.wdotFlFFC = (self.barrierObj.pcentFFC / 100.0) * self.wdotFl self.wdotFl_cInit = self.wdotFl - self.wdotFlFFC self.wdotTot_cInit = self.wdotOx + self.wdotFl_cInit else: self.wdotFlFFC = 0.0 self.wdotFl_cInit = self.wdotFl self.wdotTot_cInit = self.wdotTot self.IspDel = self.FvacTotal / self.wdotTot self.IspDelPulse = self.IspDel * self.effObj('Pulse') if self.add_barrier: # if barrier is analysed, assume it is in addition to user input effERE effFFC = self.IspDel / self.IspDel_core self.effObj.set_value('FFC', effFFC, value_src='barrier calc') self.cstarERE = self.cstarODE * self.effObj('ERE') #self.cstarDel = self.Pc * self.Atcore * self.CdThroat * 32.174 / self.wdotTot # do any nozzle ambient performance calcs here if self.Pamb < 0.000001: self.IspAmb = self.IspDel self.noz_mode = '(Pexit=%g psia)' % self.Pexit else: CfOvCfvacAtEsep, CfOvCfvac, Cfsep, CfiVac, CfiAmbSimple, CfVac, self.epsSep, self.Psep = \ sepNozzleCf(self.gammaChm, self.geomObj.eps, self.Pc, self.Pamb) #print('epsSep=%g, Psep=%g'%(self.epsSep, self.Psep)) #print('========= Pexit=%g'%self.Pexit, ' Psep=%g'%self.Psep, ' epsSep=%g'%self.epsSep) if self.Pexit > self.Psep or self.geomObj.eps < self.epsSep or self.ignore_noz_sep: # if not separated, use theoretical equation for back-pressure correction self.IspAmb = self.IspDel - self.cstarERE * self.Pamb * self.geomObj.eps / self.Pc / 32.174 #print('----------------> subtraction term =', self.cstarERE * self.Pamb * self.geomObj.eps / self.Pc / 32.174) else: # if separated, use Kalt and Badal estimate of ambient thrust coefficient # NOTE: there are better, more modern methods available IspODEepsSep, CstarODE, Tc = \ self.ceaObj.get_IvacCstrTc(Pc=self.Pc, MR=self.MRcore, eps=self.epsSep) IspODEepsSep = IspODEepsSep - self.cstarERE * self.Pamb * self.epsSep / self.Pc / 32.174 effPamb = IspODEepsSep / self.IspODE #print('--------------> effPamb=%g'%effPamb, ' IspODEepsSep=%g'%IspODEepsSep, ' IspODE=%g'%self.IspODE) self.IspAmb = effPamb * self.IspDel #print('========= Pamb=%g'%self.Pamb, ' IspAmb=%g'%self.IspAmb) # figure out mode of nozzle operation if self.Pexit > self.Psep or self.geomObj.eps < self.epsSep: if self.Pexit > self.Pamb + 0.05: self.noz_mode = 'UnderExpanded (Pexit=%g)' % self.Pexit elif self.Pexit < self.Pamb - 0.05: self.noz_mode = 'OverExpanded (Pexit=%g)' % self.Pexit else: self.noz_mode = 'Pexit=%g' % self.Pexit else: self.noz_mode = 'Separated (Psep=%g, epsSep=%g)' % ( self.Psep, self.epsSep) self.Fambient = self.FvacTotal * self.IspAmb / self.IspDel self.CfVacDel = self.FvacTotal / (self.geomObj.At * self.Pc ) # includes impact of CdThroat self.CfAmbDel = self.Fambient / (self.geomObj.At * self.Pc ) # includes impact of CdThroat def summ_print(self): """ print to standard output, the current state of CoreStream instance. """ print(self.get_summ_str()) def get_summ_str(self, alpha_ordered=True, numbered=False, add_trailer=True, fillchar='.', max_banner=76, intro_str=''): """ return string of the current state of CoreStream instance. """ M = self.get_model_summ_obj() Me = self.effObj.get_model_summ_obj() se = '\n' + Me.summ_str( alpha_ordered=False, fillchar=' ', assumptions_first=False) if self.add_barrier: Mb = self.barrierObj.get_model_summ_obj() sb = '\n' + Mb.summ_str(alpha_ordered=alpha_ordered, numbered=numbered, add_trailer=add_trailer, fillchar=fillchar, max_banner=max_banner, intro_str=intro_str) else: sb = '' return M.summ_str(alpha_ordered=alpha_ordered, numbered=numbered, add_trailer=add_trailer, fillchar=fillchar, max_banner=max_banner, intro_str=intro_str) + se + sb def get_html_str(self, alpha_ordered=True, numbered=False, intro_str=''): M = self.get_model_summ_obj() Me = self.effObj.get_model_summ_obj() se = '\n' + Me.html_table_str(alpha_ordered=False, assumptions_first=False) if self.add_barrier: Mb = self.barrierObj.get_model_summ_obj() sb = '\n' + Mb.html_table_str(alpha_ordered=alpha_ordered, numbered=numbered, intro_str=intro_str) else: sb = '' return M.html_table_str( alpha_ordered=alpha_ordered, numbered=numbered, intro_str=intro_str)\ + se + sb def get_model_summ_obj(self): """ return ModelSummary object for current state of CoreStream instance. """ M = ModelSummary('%s/%s Core Stream Tube' % (self.oxName, self.fuelName)) M.add_alt_units('psia', ['MPa', 'atm', 'bar']) M.add_alt_units('lbf', 'N') M.add_alt_units('lbm/s', 'kg/s') M.add_alt_units('ft/s', 'm/s') M.add_alt_units('sec', ['N-sec/kg', 'km/sec']) M.add_alt_units('degR', ['degK', 'degC', 'degF']) M.add_param_fmt('Pexit', '%.4f') M.add_param_fmt('Pc', '%.1f') M.add_out_category('') # show unlabeled category 1st def add_param(name, desc='', fmt='', units='', value=None, category=''): if name in self.inp_unitsD: units = self.inp_unitsD[name] if desc == '' and name in self.inp_descD: desc = self.inp_descD[name] if value is None: value = getattr(self, name) if self.is_inputD.get(name, False): M.add_inp_param(name, value, units, desc, fmt=fmt) else: M.add_out_param(name, value, units, desc, fmt=fmt, category=category) for name in self.is_inputD.keys(): if name not in ['pcentFFC', 'ko', 'geomObj', 'effObj']: add_param(name) # parameters that are NOT attributes OR are conditional if self.add_barrier: add_param('FvacBarrier', units='lbf', desc='vacuum thrust due to barrier stream tube') if self.Pamb > 14.5: add_param('Fambient', units='lbf', desc='total sea level thrust') add_param('IspAmb', units='sec', desc='delivered sea level Isp') M.add_out_comment('Fambient', '%s' % self.noz_mode) M.add_out_comment('IspAmb', '%s' % self.noz_mode) elif self.Pamb > 0.0: add_param('Fambient', units='lbf', desc='total ambient thrust') add_param('IspAmb', units='sec', desc='delivered ambient Isp') M.add_out_comment('Fambient', '%s' % self.noz_mode) M.add_out_comment('IspAmb', '%s' % self.noz_mode) if self.effObj('Pulse') < 1.0: add_param('IspDelPulse', units='sec', desc='delivered pulsing Isp') if self.CdThroat_method != 'default': M.add_inp_comment('CdThroat', '(%s)' % self.CdThroat_method) if self.add_barrier: add_param('wdotFlFFC', units='lbm/s', desc='fuel film coolant flow rate injected at perimeter', category='At Injector Face') add_param( 'wdotFl_cInit', units='lbm/s', desc='initial core fuel flow rate (before any entrainment)', category='At Injector Face') add_param( 'wdotTot_cInit', units='lbm/s', desc='initial core total flow rate (before any entrainment)', category='At Injector Face') add_param( 'wdotTot_b', units='lbm/s', desc='total barrier propellant flow rate (includes entrained)', category='After Entrainment') add_param('wdotOx_b', units='lbm/s', desc='barrier oxidizer flow rate (all entrained)', category='After Entrainment') add_param('wdotFl_b', units='lbm/s', desc='barrier fuel flow rate (FFC + entrained)', category='After Entrainment') add_param( 'wdotTot_c', units='lbm/s', desc= 'total final core propellant flow rate (injected - entrained)', category='After Entrainment') add_param( 'wdotOx_c', units='lbm/s', desc='final core oxidizer flow rate (injected - entrained)', category='After Entrainment') add_param('wdotFl_c', units='lbm/s', desc='final core fuel flow rate (injected - entrained)', category='After Entrainment') #add_param('xxx', units='xxx', desc='xxx') return M
class ceaRocket: def __init__(self, title, oxidizer, fuel, Pcham, Pambient, Mr, mdot, l_star, cham_d, conv_angle, div_angle, wall_temp, nozzle_type, r1=0.05, r2=0.03, r3=0.025, contourStep=1e-4): self.title = title self.nozzle_type = nozzle_type self.Mr = Mr self.ambientP = Pambient self.cea = CEA_Obj(oxName=oxidizer, fuelName=fuel) chems = ChemistryCEA.create(self.cea, Pcham, self.Mr, pAmbient=Pambient) self.inj = None # injector self.cham = chems[0] # converging starts (end of chamber) self.thr = chems[1] # throat self.exit = chems[2] # exit self.mdot = mdot self.Lstar = l_star self.cham.d = cham_d #diameter of the chamber self.wall_temp = wall_temp self.contourPoints = None self.contour = None self.area_arr = None self.mach_arr = [] self.pressure_arr = None self.temp_arr = None self.density_arr = None self.h_g_arr = [] self.heat_flux_arr = [] self.conv_angle = conv_angle self.divergence_angle = div_angle #self.exit.ae = self.findAe(self.cham.p, self.Mr, self.ambientP) #throat #self.thr.gam = self.cea.get_Throat_MolWt_gamma(Pc=self.cham.p, MR=self.Mr, eps=self.exit.ae)#fix #self.thr.t = None #fix self.total_watts = 0 self.r1 = r1 self.r2 = r2 self.r3 = r3 self.contourStep = contourStep #unit conversions #bar to Pa self.cham.p = self.cham.p * 100000 # Specific impulse in seconds #self.isp_s = self.exit.isp / 9.8 self.thr.a = throatAreaEquation(self.mdot, self.cham.p, self.thr.t, self.thr.rbar, self.thr.gam) # p is in atm, conversion constant to Pa, might change to Pa later. area is in m^2 # Nozzle Exit Area and diameters via Expansion Ratio and self.exit.a = self.thr.a * self.exit.aeat self.thr.d = 2 * math.sqrt(self.thr.a / math.pi) self.exit.d = 2 * math.sqrt(self.exit.a / math.pi) # Thrust by fundamental rocket eq F = mdot * exhaust_velocity self.thrust = self.mdot * self.exit.isp # + self.a_noz*(self.p-self.p_amb) not included as sea level expanded # Total Chamber Volume via Characteristic Length # NOTE: this volume does not take injector and contour volumes into consideration and is theirfor not completly accurate self.chamber_volume = self.Lstar * self.thr.a self.chamber_length = self.chamber_volume / (math.pi * (self.cham.d / 2)**2) self.Cstar = self.cham.p * self.thr.a / self.mdot # new methods for generating chamber and nozzle contour self.contourPoints, self.contour = self.nozzleGeneration() # this generates the chamber and nozzle contour that is used for calculations #self.my_contourPoints(r1, r2, r3) #self.genContour(r1, r2, r3, step) # temporarily turn off all convergence dependent functions self.area_arr = self.areas(self.contour) self.solveMach() # self.areaMach() self.tempPressureDensity() self.calcBartz() self.calcHeatFlux() self.totalWatts() # this generates the points that the gencontour function uses to make functions between # the points are referenced from left to right in the graph def findAe(self, Pcham, Mr, Pambient): aeGuess = 1000 * 2 step = aeGuess / 2 guessPress = 0.0 while (guessPress - Pambient) > 0.01: if guessPress < Pambient: aeGuess -= step else: aeGuess += step pressRatio = self.cea.get_PcOvPe(Pc=Pcham, MR=Mr, eps=aeGuess) guessPress = Pcham / pressRatio return aeGuess def TOPnozzleCoefficients( self, Rn, Re, Xn, thetaN, thetaE): # finds a,b,c for TOP parobolic function A = np.array([[2 * Rn, 1, 0], [2 * Re, 1, 0], [Rn**2, Rn, 1]]) B = np.array([1 / np.tan(thetaN), 1 / np.tan(thetaE), Xn]) X = np.linalg.solve(A, B) return X def nozzleGeneration( self ): #this function creates contour points and functions for the chamber and nozzle geometry #this first section sets up points for the chamber and throat. with the left being the chamber the right being the exit, the points go in order of a, b, c, d, o, n, e r1 = self.r1 * self.thr.d / 2 r2 = self.r2 * self.thr.d / 2 o = [0, self.thr.d / 2] d = [ -r2 * np.sin(self.conv_angle), o[1] + r2 * (1 - np.cos(self.conv_angle)) ] c = [None, self.cham.d / 2 - (r1 * (1 - np.cos(self.conv_angle)))] c[0] = d[0] - (c[1] - d[1]) * (np.sin(math.pi / 2 - self.conv_angle) / np.sin(self.conv_angle)) b = [c[0] - (r1 * np.sin(self.conv_angle)), self.cham.d / 2] a = [b[0] - self.chamber_length, self.cham.d / 2] if self.nozzle_type == 'conical': # this sets the points and equations for a conical nozzle r3 = self.r3 * self.thr.d / 2 n = [ r3 * np.sin(self.divergence_angle), o[1] + r3 * np.sin(1 - np.cos(self.divergence_angle)) ] e = [ n[0] + ((self.exit.d / 2) - n[1]) * np.sin(math.pi / 2 - self.divergence_angle) / np.sin(self.divergence_angle), self.exit.d / 2 ] #print('n variable: {}'.format(n)) #print('e variable: {}'.format(e)) contourPoints = [a, b, c, d, o, n, e] # temporary nozzleCurve = lambda x: ( (contourPoints[5][1] - contourPoints[6][1]) / (contourPoints[5][0] - contourPoints[6][0])) * ( x - contourPoints[5][0]) + contourPoints[5][1] elif self.nozzle_type == 'bell80': # this sets the points and equations for an 80% bell nozzle r3 = self.r3 * self.thr.d / 2 thetaE = 7 * np.pi / 180 # theta values found in table... hard coded temporarily thetaN = 33 * np.pi / 180 n = [r3 * np.sin(thetaN), o[1] + r3 * np.sin(1 - np.cos(thetaN))] e = [ 0.8 * (((math.sqrt(self.exit.aeat) - 1) * self.thr.d / 2 / np.tan(15 * np.pi / 180))), self.exit.d / 2 ] # specificly for 80% bell nozzles contourPoints = [a, b, c, d, o, n, e] # temporary A = np.array([[2 * n[1], 1, 0], [2 * e[1], 1, 0], [n[1]**2, n[1], 1]]) B = np.array([1 / np.tan(thetaN), 1 / np.tan(thetaE), n[0]]) X = np.linalg.solve(A, B) aa = X[0] bb = X[1] cc = X[2] #print('exit r:{}'.format(self.exit.d/2)) #print('thr r:{}'.format(self.thr.d/2)) #print('exit area ratio:{}'.format(self.exit.ae)) #print('n variable: {}'.format(n)) #print('e variable: {}'.format(e)) #print('A variable: {}'.format(A)) #print('B variable: {}'.format(B)) #print('X variable: {}'.format(X)) #print('a: {}\nb: {}\nc: {}'.format(aa,bb,cc)) nozzleCurve = lambda x: (-X[1] + (X[1]**2 - 4 * X[0] * (X[ 2] - x))**0.5) / (2 * X[0]) # might need to change sign elif self.nozzle_type == 'dualbell': #work in progress, this sets the points and equations for a duel bell nozzle, in this there is an extra point 'm' between the n and e points r3 = self.r3 * self.thr.d / 2 thetaE1 = 7 * np.pi / 180 # theta values found in table... hard coded temporarily thetaN1 = 33 * np.pi / 180 thetaE2 = 7 * np.pi / 180 thetaN2 = 33 * np.pi / 180 curvePercent1 = .7 curvePercent2 = .8 curve1ae = None #low altitude optimized area ratio curve2ae = self.exit.aeat curve1rad = None #low altitude optimized radius curve2rad = self.exit.d / 2 n = [r3 * np.sin(thetaN1), o[1] + r3 * np.sin(1 - np.cos(thetaN1))] m = [ curvePercent1 * (((math.sqrt(curve1ae) - 1) * self.thr.d / 2 / np.tan(15))), curve1rad ] e = [ curvePercent2 * (((math.sqrt(curve2ae) - 1) * self.thr.d / 2 / np.tan(15))), curve2rad ] contourPoints = [a, b, c, d, o, n, m, e] # temporary #X1 = TOPnozzleCoefficients(n[1], m[1], n[0], thetaN1, thetaE1) A = np.array([[2 * n[1], 1, 0], [2 * m[1], 1, 0], [n[1]**2, n[1], 1]]) B = np.array([1 / np.tan(thetaN1), 1 / np.tan(thetaE1), n[0]]) X1 = np.linalg.solve(A, B) nozzleCurve1 = lambda x: (-X1[1] + math.sqrt(X1[1]**2 - 4 * X1[ 0] * (X1[2] - x))) / 2 * X1[0] A = np.array([[2 * m[1], 1, 0], [2 * e[1], 1, 0], [n[1]**2, n[1], 1]]) B = np.array([1 / np.tan(thetaN2), 1 / np.tan(thetaE2), m[0]]) X2 = np.linalg.solve(A, B) nozzleCurve2 = lambda x: (-X2[1] + math.sqrt(X2[1]**2 - 4 * X2[ 0] * (X2[2] - x))) / 2 * X2[0] else: #this runs if the nozzle type input does not match any of the above nozzle types print("invalid nozzle type") functions = [ lambda x: contourPoints[0][1], lambda x: np.sqrt(r1**2 - ( x - contourPoints[1][0])**2) + contourPoints[1][1] - r1, lambda x: ((contourPoints[2][1] - contourPoints[3][1]) / (contourPoints[2][0] - contourPoints[3][0])) * (x - contourPoints[2][0]) + contourPoints[2][1], lambda x: -np.sqrt(r2**2 - (x - contourPoints[4][0])**2 ) + contourPoints[4][1] + r2, lambda x: -np.sqrt(r3**2 - (x + contourPoints[4][0])**2 ) + contourPoints[4][1] + r3, nozzleCurve ] num = np.int32( np.rint((contourPoints[6][0] - contourPoints[0][0]) / self.contourStep)) x = np.array([]) y = np.array([]) for i, fun in enumerate(functions): temp_x = np.linspace(contourPoints[i][0], contourPoints[i + 1][0], num) f = np.vectorize(fun) #if i == 5: # print('x:{}\ty:{}'.format(temp_x,f(temp_x))) y = np.append(y, f(temp_x)) x = np.append(x, temp_x) contour = np.array([x, y]) #exports contour points for use in cad locs = ['inj', 'b', 'c', 'd', 'o', 'n', 'e'] xy = ['x', 'y'] txtout = open('dims.txt', 'w') for i in range(len(contourPoints)): for j in range(2): txtout.write('"{0}_{1}"= {2}\n'.format( locs[i], xy[j], contourPoints[i][j] / 0.0254)) return contourPoints, contour ''' def my_contourPoints(self, r1=0.05, r2=0.03, r3=0.025): o = [0,self.thr.d / 2] d = [-r2 * np.sin(self.conv_angle),o[1] + r2 * (1 - np.cos(self.conv_angle))] n = [r3 * np.sin(self.divergence_angle), o[1] + r3 * np.sin(1 - np.cos(self.divergence_angle))] e = [n[0] + ((self.exit.d / 2) - n[1]) * np.sin(math.pi/2 - self.divergence_angle)/np.sin(self.divergence_angle), self.exit.d / 2] a = [None, self.cham.d / 2] b = [None, self.cham.d / 2] c = [None,self.cham.d / 2 - (r1 * (1 - np.cos(self.conv_angle)))] c[0] = d[0] - (c[1] - d[1]) * (np.sin(math.pi/2 - self.conv_angle)/np.sin(self.conv_angle)) b[0] = c[0] - (r1 * np.sin(self.conv_angle)) a[0] = b[0] - self.chamber_length self.contourPoints = [a, b, c, d, o, n, e] locs = ['inj', 'b', 'c', 'd', 'o', 'n', 'e'] xy = ['x', 'y'] txtout = open('dims.txt','w') for i in range(len(self.contourPoints)): for j in range(2): txtout.write('"{0}_{1}"= {2}\n'.format(locs[i], xy[j], self.contourPoints[i][j]/0.0254)) def genContour(self, r1=0.05, r2=0.03, r3=0.025, step=1e-4): # This is the function that draws the discrete contour # these functions are referenced from left to right of the graph functions = [ lambda x: self.contourPoints[0][1], lambda x: np.sqrt(r1 ** 2 - (x - self.contourPoints[1][0]) ** 2) + self.contourPoints[1][1] - r1, lambda x: ((self.contourPoints[2][1] - self.contourPoints[3][1]) / (self.contourPoints[2][0] - self.contourPoints[3][0])) * (x - self.contourPoints[2][0]) + self.contourPoints[2][1], lambda x: -np.sqrt(r2 ** 2 - (x - self.contourPoints[4][0]) ** 2) + self.contourPoints[4][1] + r2, lambda x: -np.sqrt(r3 ** 2 - (x + self.contourPoints[4][0]) ** 2) + self.contourPoints[4][1] + r3, lambda x: ((self.contourPoints[5][1] - self.contourPoints[6][1]) / (self.contourPoints[5][0] - self.contourPoints[6][0])) * (x - self.contourPoints[5][0]) + self.contourPoints[5][1] ] # 1: straight line # 2: circle # 3: straight line # 4: circle # 5: circle # 6: straight line num = np.int32(np.rint((self.contourPoints[6][0] - self.contourPoints[0][0]) / step)) x = np.array([]) y = np.array([]) for i, fun in enumerate(functions): temp_x = np.linspace(self.contourPoints[i][0], self.contourPoints[i + 1][0], num) f = np.vectorize(fun) y = np.append(y, f(temp_x)) x = np.append(x, temp_x) self.contour = np.array([x, y]) ''' #array functions: #this finds area over the contour def areas(self, contour): area_arr = contour.copy() area_arr[1, :] = (area_arr[1, :]** 2) * np.pi # this is just pi*r^2 in array form return area_arr def solveMach(self): def solveMatchForAreaRatio(area_ratio, mach_guess=0.7): def machEqn(mach): # return mach * area_ratio + 10 return (2 / (self.thr.gam + 1) * (1 + (self.thr.gam - 1) / 2 * mach**2))**( (self.thr.gam + 1) / (2 * (self.thr.gam - 1))) - mach * area_ratio return fsolve(machEqn, mach_guess) last = 0.7 for [x, area] in self.area_arr.transpose(): if x > 0: last = last + 1 [mach] = solveMatchForAreaRatio(area / self.thr.a, last) self.mach_arr.append([x, mach]) last = mach self.mach_arr = np.array(self.mach_arr).transpose() def temp_eq(self, mach): #NOTE: stagnation values need improvment gam = self.thr.gam t_stag = self.cham.t * (1 + ((gam - 1) / 2 * self.cham.mach**2)) # Note: ok technically, yes, the stagnation temperature needs to account for # gas velocity, but in our assumptions, t_0 assumed == t_cham as given by CEA #t_stag = self.inj.t myreturn = t_stag * (1 + ((gam - 1) / 2 * mach**2))**(-1) return myreturn def pressure_eq(self, mach): gam = self.thr.gam # CHECK THIS!!! p_stag = self.cham.p * (1 + ( (gam - 1) / 2 * self.cham.mach**2))**(gam / (gam - 1)) #p_stag = self.inj.p myreturn = p_stag * (1 + ((gam - 1) / 2 * mach**2))**(-gam / (gam - 1)) return myreturn def density_eq(self, mach): #need to find chamber density gam = self.cham.gam # CHECK THIS!!! d_stag = self.cham.rho * (1 + ( (gam - 1) / 2 * self.cham.mach**2))**(1 / (gam - 1)) myreturn = d_stag * (1 + ((gam - 1) / 2 * mach**2))**(-1 / (gam - 1)) return myreturn def tempPressureDensity(self): self.pressure_arr = self.mach_arr.copy() self.temp_arr = self.mach_arr.copy() #self.density_arr = self.mach_arr.copy() count = 0 for mach in self.mach_arr[1, :]: self.temp_arr[1, count] = self.temp_eq(mach) self.pressure_arr[1, count] = self.pressure_eq(mach) #self.density_arr[1,count] = self.density_eq(mach) count += 1 def filewrite(self, filename): output = open(filename, "w") offset = self.contour[0, 0] for i in range(len(self.contour[0])): self.contour[0, i] += -offset output.write("X,Y,MACH,TEMP,Pressure,h_g,FLUX\n") for i in range(len(self.contour[1, :])): output.write( "{:.4f},{:.4f},{:.4f},{:.4f},{:.4f},{:.4f},{:.4f}\n".format( self.contour[0, i], self.contour[1, i], self.mach_arr[1, i], self.temp_arr[1, i], self.pressure_arr[1, i], self.h_g_arr[1, i], self.heat_flux_arr[1, i])) output.close() def calcBartz(self): self.h_g_arr = self.contour.copy() for i in range(len(self.h_g_arr[0])): self.h_g_arr[1, i] = bartz(self.thr.d, self.cham.p, self.Cstar, self.contour[1, i] * 2, self.cham.cp * 1000, 0.85452e-4, self.temp_arr[1, i], self.wall_temp) def calcHeatFlux(self): self.heat_flux_arr = self.h_g_arr.copy() for i in range(len(self.heat_flux_arr[0])): self.heat_flux_arr[1, i] = self.h_g_arr[1, i] * (self.temp_arr[1, i] - self.wall_temp) def totalWatts(self): for i in range(len(self.heat_flux_arr[0]) - 1): self.total_watts = self.total_watts + abs(self.area_arr[ 0, i + 1] - self.area_arr[0, i]) * self.heat_flux_arr[1, i] ################################################################## def variablesDisplay(self): print("{}{}:{}".format('\033[33m', self.title, '\033[0m')) print("Chamber Length: {0:.2f} in".format(self.chamber_length / 0.0254)) print("Chamber Diameter: {0:.2f} in".format(self.cham.d / 0.0254)) print("Exit Diameter: {0:.2f} in".format(self.exit.d / 0.0254)) print("Throat Diameter: {0:.2f} in".format(self.thr.d / 0.0254)) print("Total Length: {0:.2f} in".format( (self.contourPoints[6][0] - self.contourPoints[0][0]) / 0.0254)) print("Volume: {0:.2f} cc".format(self.chamber_volume * 1000000)) print("Thrust: {0:.2f} N".format(self.thrust)) print("Chamber heat flux constant: {0:.2f} W/m^2K".format( self.h_g_arr[1, 1])) print("Chamber heat flux W/m^2: {0:.2f} W/m^2".format( self.heat_flux_arr[1, 1])) print("Total Watts: {0:.2f} W".format(self.total_watts)) print("Mass Flow Rate: {0:.2f} mdot".format(self.mdot)) print("chamber pressure: {0:.2f} bar".format(self.pressure_arr[1, 1] / 100000)) print() def graphDisplay(self, pressure_units='bar', distance_units='in'): #temperature units? if (pressure_units == 'bar'): Pcon = 100000 #bar elif (pressure_units == 'atm'): Pcon = 101325 elif (pressure_units == 'psi'): Pcon = 6894.76 else: Pcon = 1 if (distance_units == 'in'): Dcon = 39.3701 elif (distance_units == 'cm'): Dcon = 100 elif (distance_units == 'mm'): Dcon = 1000 else: Dcon = 1 self.contour = self.contour * Dcon fig, axs = plt.subplots(2, 1, figsize=(8, 10.5)) axs[0].set_title("Nozzle Geometry") axs[0].plot(self.contour[0], self.contour[1], label="self Contour") axs[0].set(xlabel="Axial Position (in)", ylabel="Radial Distance (in)") axs[0].axis('equal') secaxs = axs[0].twinx() secaxs.plot(self.mach_arr[0] * Dcon, self.mach_arr[1], label="Mach Number", color="green") secaxs.set(ylabel="Mach Number (M)") axs[0].legend(loc=(0, 1)) secaxs.legend(loc=(0.75, 1)) axs[1].plot(self.temp_arr[0], self.temp_arr[1], color="orange", label="Temperature") axs[1].set(ylabel="Gas Core Temperature (K)") secaxs1 = axs[1].twinx() secaxs1.plot(self.pressure_arr[0], self.pressure_arr[1] * Pcon, color="purple", label="Pressure") secaxs1.set(ylabel="Pressure (bar)", xlabel="Axial Position (m)") axs[1].legend(loc=(0, 1)) secaxs1.legend(loc=(0.8, 1)) #--------------------------------------------------------- fig2, axs2 = plt.subplots(2, 1, figsize=(8, 10.5)) axs2[0].set_title("Nozzle Geometry") axs2[0].plot(self.contour[0], self.contour[1]) axs2[0].set(xlabel="Axial Position (in)", ylabel="Radial Distance (in)") axs2[0].axis('equal') axs2[1].plot(self.h_g_arr[0], self.h_g_arr[1], label="h", color="blue") axs2[1].set(ylabel="Coefficient of Heat Transfer (W/m^2*K)", xlabel="Axial Position (m)") sax = axs2[1].twinx() sax.plot(self.heat_flux_arr[0], self.heat_flux_arr[1], label="flux", color="g") sax.set(ylabel="Heat Flux Rate (W/m^2)", xlabel="Axial Position (m)") axs2[1].legend(loc=(0, 1)) sax.legend(loc=(0.8, 1)) self.contour = self.contour / Dcon self.filewrite("dataTest.txt") plt.show()