set_rocketcea_data_dir(test_dir) print('ROCKETCEA_DATA_DIR =', get_rocketcea_data_dir()) # compare 200 psia (13.7895 bar) results C = CEA_Obj(oxName='LOX', fuelName='LH2', pressure_units='bar', cstar_units='m/s', temperature_units='K', isp_units='N-s/kg') IspVac, Cstar, Tcomb = C.get_IvacCstrTc(Pc=13.7895, MR=1.0, eps=40.0, frozen=0, frozenAtThroat=0) print('IspVac=%g, Cstar=%g, Tcomb=%g' % (IspVac, Cstar, Tcomb)) print(' .... converted to English ....') print('IspVac=%g, Cstar=%g, Tcomb=%g' % (IspVac * 0.101972, Cstar * 3.28084, Tcomb * 1.8)) Cd = CEA_Obj_default(oxName='LOX', fuelName='LH2') IspVac, Cstar, Tcomb = Cd.get_IvacCstrTc(Pc=200.0, MR=1.0, eps=40.0, frozen=0, frozenAtThroat=0) print('IspVac=%g, Cstar=%g, Tcomb=%g' % (IspVac, Cstar, Tcomb))
class HybridCEALookup(): """ Class to generate and manage N2O-ABS Hybrid engine CEA data for Pc and o/f ratios """ __depth = 10 def __init__(self): self.datapathname = './.lookup.npy' self.configpathname = './.lookupconfig.json' self.data = None self.config = None self.__loaded = False # This str provides molecular structure, wt% of fuel, heat of formation, and density information to underlying CEA code fuel_str = """ fuel ABS(S) C 3.85 H 4.85 N 0.43 wt%=100.0 h,kJ=62.63 t(k)=298.15 rho,kg=1224 """ add_new_fuel('ABS', fuel_str) self.cea = CEA_Obj(oxName='N2O', fuelName='ABS', cstar_units='m/s', pressure_units='Pa', temperature_units='K', sonic_velocity_units='m/s', enthalpy_units='J/kg', density_units='kg/m^3', specific_heat_units='J/kg-K', viscosity_units='poise', thermal_cond_units='W/cm-degC', make_debug_prints=True) def generate(self, pcmin=1, pcmax=1000, ofmin=1, ofmax=40, pcint=1, ofint=1): """ Generate NxMx8 array of chamber thermodynamic and transport properties from min to max (inclusive).\n Use CEA to produce the following data at each pc + ofratio combination (8 values)\n pc : chamber pressure (Pa)\n of : mixture ratio\n Tc : chamber temperature (K)\n k : specific heat ratio\n MW : molecular weight (g / mol)\n Pr : Prandtl number\n Cp : specific heat capacity (J/kg-K)\n mu : viscocity (poise)\n cstar : characteristic velocity (m/s)\n isp : isp (s) """ print('Using N2O, ABS, with SI units') if not self.uses(pcmin=pcmin, pcmax=pcmax, pcint=pcint, ofmin=ofmin, ofmax=ofmax, ofint=ofint): try: # Construct NxMx8 numpy array to hold all data xdim, ydim = self.__getDim(pcmin, pcmax, pcint, ofmin, ofmax, ofint) data = np.zeros(shape=(xdim, ydim, HybridCEALookup.__depth), dtype=float) print( f'Generating lookup table, {xdim}x{ydim}x{HybridCEALookup.__depth}' ) # Enumerate over pressure and ofratio ranges and compute CEA results for (pc_i, pc) in enumerate(np.linspace(pcmin, pcmax, xdim)): for (of_i, of) in enumerate(np.linspace(ofmin, ofmax, ydim)): (cp, mu, _, pr) = self.cea.get_Chamber_Transport(pc, of) (mw, k) = self.cea.get_Chamber_MolWt_gamma(pc, of) (isp, cstar, tc) = self.cea.get_IvacCstrTc(pc, of) data[pc_i, of_i, :] = np.array( [pc, of, tc, k, mw, pr, cp, mu, cstar, isp], dtype=float) # assign data to obj and save to cache self.data = data try: config = { "pcmin": pcmin, "pcmax": pcmax, "pcint": pcint, "ofmin": ofmin, "ofmax": ofmax, "ofint": ofint, "pcnum": xdim, "ofnum": ydim, "depth": HybridCEALookup.__depth } self.config = config with open(os.path.abspath(self.datapathname), 'wb') as f: np.save(f, data, allow_pickle=True) with open(os.path.abspath(self.configpathname), 'w') as f: json.dump(config, f) print( f'Lookup table cached, pcrange: {pcmin}-{pcmax}, ofrange: {ofmin}-{ofmax}' ) except IOError as ioerror: print(ioerror) print("uh oh") except Exception as exception: raise exception else: print( f'Using lookup table from cached:\npcrange: {pcmin}-{pcmax}\nofrange: {ofmin}-{ofmax}' ) def open(self): """ Open cached lookup table """ try: with open(os.path.abspath(self.datapathname), 'rb') as f: self.data = np.load(f, allow_pickle=True) with open(os.path.abspath(self.configpathname), 'r') as f: self.config = json.load(f) self.__loaded = True return True except Exception: # Catch all and return false return False def flushCache(self): """ """ try: with open(os.path.abspath(self.datapathname), 'wb') as f: np.save(f, []) with open(os.path.abspath(self.configpathname), 'w') as f: json.dump({}, f) except Exception as exception: print(exception) def get(self, pc, of): """ Retrieve CEA outputs at any chamber pressure """ if pc < self.config["pcmin"] or pc > self.config["pcmax"]: print( f'Pc: {pc}, Pcmin: {self.config["pcmin"]}, Pcmax: {self.config["pcmax"]}' ) raise Exception("Chamber pressure out of bounds") elif of < self.config["ofmin"] or of > self.config["ofmax"]: print( f'OF: {of}, OFmin: {self.config["ofmin"]}, OFmax: {self.config["ofmax"]}' ) raise Exception("OF ratio out of bounds") # Convert to chamber pressure and OF ratio floating point index pc_i = (pc - self.config["pcmin"]) / (self.config["pcmax"] - self.config["pcmin"]) * ( self.config["pcnum"] - 1) of_i = (of - self.config["ofmin"]) / (self.config["ofmax"] - self.config["ofmin"]) * ( self.config["ofnum"] - 1) # Get chamber pressure lower and upper bounds (1d arr) pc_li = floor(pc_i) pc_hi = ceil(pc_i) # OF ratio lower and upper bounds (1d arr) of_li = floor(of_i) of_hi = ceil(of_i) # Get distance of chamber pressure and OF ratio from closest lower index pc_x = (pc_i - pc_li) # / self.config["pcint"] of_x = (of_i - of_li) # / self.config["ofint"] # Interpolate between pressure data pc_lerp_of_l = self.data[pc_li, of_li, :] + pc_x * ( self.data[pc_hi, of_li, :] - self.data[pc_li, of_li, :]) pc_lerp_of_h = self.data[pc_li, of_hi, :] + pc_x * ( self.data[pc_hi, of_hi, :] - self.data[pc_li, of_hi, :]) # Interpolate between OF ratio data and return result return pc_lerp_of_l + of_x * (pc_lerp_of_h - pc_lerp_of_l) def getPc(self, pc): """ Return a row vector of OF Ratio results at a provided chamber pressure """ if pc < self.config["pcmin"] or pc > self.config["pcmax"]: raise Exception("Chamber pressure out of bounds") pc_i = (pc - self.config["pcmin"]) / (self.config["pcmax"] - self.config["pcmin"]) * ( self.config["pcnum"] - 1) # Get chamber pressure lower and upper bounds (1d arr) pc_li = floor(pc_i) pc_hi = ceil(pc_i) pc_x = (pc_i - pc_li) # / self.config["pcint"] return self.data[pc_li, :, :] + pc_x * (self.data[pc_hi, :, :] - self.data[pc_li, :, :]) def uses(self, pcmin=200, pcmax=800, pcint=10, ofmin=1, ofmax=30, ofint=1): """ Check the cached lookup table configuration against specified options\n """ if self.__loaded: xdim, ydim = self.__getDim(pcmin, pcmax, pcint, ofmin, ofmax, ofint) useConfig = { "pcmin": pcmin, "pcmax": pcmax, "pcint": pcint, "ofmin": ofmin, "ofmax": ofmax, "ofint": ofint, "pcnum": xdim, "ofnum": ydim, "depth": HybridCEALookup.__depth } return useConfig == self.config return False def getConfig(self): """ Get current lookup table configuration """ if self.__loaded: return self.config else: return None def __getDim(self, pcmin, pcmax, pcint, ofmin, ofmax, ofint): xdim = ceil((pcmax - pcmin) / pcint) + 1 ydim = ceil((ofmax - ofmin) / ofint) + 1 return xdim, ydim