diff --git a/HRAP - Python/hrap_python/.gitignore b/HRAP - Python/hrap_python/.gitignore new file mode 100644 index 0000000..2714d90 --- /dev/null +++ b/HRAP - Python/hrap_python/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +**/__pycache__ \ No newline at end of file diff --git a/HRAP - Python/hrap_python/README.md b/HRAP - Python/hrap_python/README.md new file mode 100644 index 0000000..00b0bcf --- /dev/null +++ b/HRAP - Python/hrap_python/README.md @@ -0,0 +1,65 @@ +# HRAP Python + +## Purpose +This package provides a Python port of the HRAP utility, allowing users to simulate hybrid rocket engines without the need for a MATLAB license. The package exposes a core API which can be used to generate custom scripts, as well as a GUI for ease of use. + +## Getting Started +To run a simulation, you'll first need to generate a `SimulationConfig` and `InitialConditions`, which are both pretty self-explanatory. A `SimulationConfig` is composed of several child components corresponding to the different parts of a hybrid engine: `PropellantConfig`, `HardwareConfig`, and `NozzleConfig`. `PropellantConfig` requires the creation of a `PropellantData` object, which loads tabulated propellant data from one of the `.npz` files in the `propellants` folder. All told, that looks like: + +```python +from api.core import sim_loop +from api.models import * +from api.sim import shift_OF + +material = MaterialData('propellants/Paraffin.npz') +prop_config = PropellantConfig(ID=LengthValue(2.0, LengthUnit.INCHES), + OD=LengthValue(4.0, LengthUnit.INCHES), + length=LengthValue(12.0, LengthUnit.INCHES), + cstar_eff=1.0, + material=material, + regression_model=shift_OF) + +hardware_config = HardwareConfig(tank_volume=VolumeValue(7000.0, VolumeUnit.CU_CENTIMETERS), + chamber_volume=None, + injector_Cd=0.6, + injector_D=LengthValue(0.375, LengthUnit.INCHES), + injector_N=1, + vent_state=VentConfig.EXTERNAL, + vent_Cd=0.6, + vent_D=LengthValue(0.028, LengthUnit.INCHES)) + +nozzle_config = NozzleConfig(Cd=1.0, + throat=LengthValue(1.2, LengthUnit.INCHES), + exp_ratio=4.0, + efficiency=1.0) + +sim_config = SimulationConfig(timestep=0.001, + max_run_time=10.0, + max_burn_time=10.0, + ambient_pressure=PressureValue(1.0, PressureUnit.ATM), + hardware=hardware_config, + prop=prop_config, + nozzle=nozzle_config) +``` + +Initial conditions are generated in much the same way: +```python +init_conditions = InitialConditions(chamber_pressure=PressureValue(1.0, PressureUnit.ATM), + mass_ox=MassValue(7.0, MassUnit.KILOGRAM), + tank_temp=TemperatureValue(63.0, TemperatureUnit.FAHRENHEIT)) +``` + +Finally, the simulation can be run by providing the constructed `SimulationConfig` and `InitialConditions` objects to `sim_loop`, which returns a `SimulationOutput` object containing all relevant simulation data. +```python +output = sim_loop(sim_config, init_conditions) +``` + +Outputs, much like inputs, will be given as a `*Value`. To convert between a list of `*Value`s to a list of conventional floats, simply use `get_as` in a list comprehension like so: +```python +thrust_data = [t.get_as(MassUnit.NEWTONS) for t in output.thrust] +``` + +The full code, along with a plotting example, is available in `demo.py` + +## Testing +Unit tests are provided to ensure that behavior is well-defined and doesn't change version-to-version. Tests are located in the `test` module, and can be run from this directory with `python3 -m unittest discover -s test -v`. Some 'magic number' values in the unit tests are copied from the MATLAB output of the program for reference. \ No newline at end of file diff --git a/HRAP - Python/python/components/__init__.py b/HRAP - Python/hrap_python/__init__.py similarity index 100% rename from HRAP - Python/python/components/__init__.py rename to HRAP - Python/hrap_python/__init__.py diff --git a/HRAP - Python/python/util/__init__.py b/HRAP - Python/hrap_python/api/__init__.py similarity index 100% rename from HRAP - Python/python/util/__init__.py rename to HRAP - Python/hrap_python/api/__init__.py diff --git a/HRAP - Python/hrap_python/api/core.py b/HRAP - Python/hrap_python/api/core.py new file mode 100644 index 0000000..d48d3db --- /dev/null +++ b/HRAP - Python/hrap_python/api/core.py @@ -0,0 +1,83 @@ +""" +HRAP Simulation Core (`hrap_python.api.core`) + +Core HRAP simulation functions +""" +from .models import * +from .sim import * + +def sim_loop(config: SimulationConfig, init_conditions: InitialConditions) -> SimulationOutput: + """ + Runs a simulation. + + Parameters + ---------- + config : SimulationConfig + The simulation configuration parameters + + init_conditions : InitialConditions + The initial conditions for the simulation state + + Returns + ------- + SimulationOutput + """ + t = 0.0 + i = 1 + dt = config.timestep + + state = SimulationState(config, init_conditions) + output = SimulationOutput(config) + + while True: + t = (i-1)*dt + i = i+1 + + sim_iteration(config, state, output) + + if state.chamber.grain_ID >= config.prop.OD: + output.end_condition = 'Fuel Depleted' + break + elif state.tank.mass_ox <= 0: + output.end_condition = 'Oxidizer Depleted' + break + elif t >= config.max_run_time: + output.end_condition = 'Max Simulation Time Reached' + break + elif state.chamber.pressure <= config.ambient_pressure: + output.end_condition = 'Burn Complete' + break + + return output + +def sim_iteration(config: SimulationConfig, state: SimulationState, output: SimulationOutput) -> None: + """ + A single iteration of a simulation. Contains all simulation state transitions for a given timestep. + + Parameters + ---------- + config : SimulationConfig + The simulation configuration parameters + + state : SimulationState + The current simulation state + + output : SimulationOutput + The tentative simulation output + + Returns + ------- + None + """ + tank(config, state) + config.prop.regression_model(config, state) + + combustion(config, state) + chamber(config, state) + nozzle(config, state) + + if config.mass_properties is not None: + mass(config, state) + + output.append_state(state) + state.time += config.timestep diff --git a/HRAP - Python/hrap_python/api/models.py b/HRAP - Python/hrap_python/api/models.py new file mode 100644 index 0000000..e3c3ada --- /dev/null +++ b/HRAP - Python/hrap_python/api/models.py @@ -0,0 +1,242 @@ +""" +HRAP Data Models (`hrap_python.api.models`) + +Configuration and state models for HRAP simulations +""" +from enum import Enum +from util.units import * +from util.nox import * +from scipy.optimize import fsolve +from typing import List +import numpy as np + +### +### CONFIGURATION MODELS +### +class VentConfig(Enum): + NONE = 1 + EXTERNAL = 2 + INTERNAL = 3 + +class MaterialData: + # TODO: Better way of loading file? + def __init__(self, filename: str): + container = np.load(filename) + for label in ['metadata', 'regression_coeff', 'OF', 'Pc', 'k', 'M', 'T']: + assert label in container, f'Data for "{label}" not found in saved combustion data file {filename}!' + # Metadata can be extended to include other mixed data - although it's unclear whether NoneTypes are supported + self.name = container['metadata'][0] + self.rho = float(container['metadata'][1]) + # All coefficients will be negative (typically -1) if a shifting-OF model is not supported + # 0 is not reliable due to floating-point precision errors (may become positive or negative) + self.regression_coeff = container['regression_coeff'] + self.OF = container['OF'] + self.Pc = container['Pc'] + self.k = container['k'] + self.M = container['M'] + self.T = container['T'] + +class PropellantConfig: + def __init__(self, ID: LengthValue, OD: LengthValue, length: LengthValue, cstar_eff: float, + material: MaterialData, regression_model, const_OF_ratio: float | None = None): + self.ID = ID.base_value # Grain ID (m) + self.OD = OD.base_value # Grain OD (m) + self.length = length.base_value # Grain Length (m) + + self.cstar_eff = cstar_eff # C-star efficiency + self.material = material # Material Data - imported from file + + assert ((material.regression_coeff[0] > 0 and const_OF_ratio is None) + or (const_OF_ratio is not None and const_OF_ratio > 0)), "Material must support shifting OF ratio, or constant OF ratio must be provided!" + + self.regression_model = regression_model + self.const_OF_ratio = const_OF_ratio # Constant OF ratio (optional) + +class HardwareConfig: + def __init__(self, tank_volume: VolumeValue, chamber_volume: VolumeValue | None, injector_Cd: float, injector_D: LengthValue, + injector_N: int, vent_state: VentConfig, vent_Cd: float | None = None, vent_D: LengthValue | None = None): + self.tank_volume = tank_volume.base_value # Tank Volume (m^3) + if chamber_volume is None: + self.chamber_volume = None + else: + self.chamber_volume = chamber_volume.base_value # Chamber Volume (m^3) - if None, computed from grain dimensions + + self.injector_cda_N = injector_Cd * d_to_a(injector_D.base_value) * injector_N + + assert vent_state == VentConfig.NONE or (vent_Cd is not None and vent_D is not None), "Must input valid vent configuration!" + self.vent_state = vent_state + self.vent_cda = vent_Cd * d_to_a(vent_D.base_value) + +class NozzleConfig: + def __init__(self, Cd: float, throat: LengthValue, exp_ratio: float, efficiency: float): + self.Cd = Cd # Coefficient of dispersion + self.throat = throat.base_value # Throat diameter (m) + self.exp_ratio = exp_ratio # Expansion Ratio (unitless) + self.efficiency = efficiency # Efficiency (>0, <1.0) + +class MassProperties: + def __init__(self, motor_mass: MassValue, motor_cg: LengthValue, tank_location: LengthValue, grain_location: LengthValue): + self.motor_mass = motor_mass.base_value # Motor Mass (kg) + self.motor_cg = motor_cg.base_value # Motor center of gravity (m) + self.tank_location = tank_location.base_value # Tank center of gravity (m) + self.grain_location = grain_location.base_value # Grain center of gravity (m) + +class SimulationConfig: + def __init__(self, timestep: float, max_run_time: float, max_burn_time: float, ambient_pressure: PressureValue, + hardware: HardwareConfig, prop: PropellantConfig, nozzle: NozzleConfig, mass_properties: MassProperties | None = None): + self.timestep = timestep + self.max_run_time = max_run_time + self.max_burn_time = max_burn_time + self.ambient_pressure = ambient_pressure.base_value + + grain_volume = d_to_a(prop.OD) * prop.length + if hardware.chamber_volume is None: + hardware.chamber_volume = grain_volume + + assert hardware.chamber_volume >= grain_volume, "Chammber cannot be smaller than fuel grain!" + + self.hardware = hardware + self.prop = prop + self.nozzle = nozzle + self.mass_properties = mass_properties + +### +### STATE MODELS +### +class InitialConditions: + def __init__(self, chamber_pressure: PressureValue, mass_ox: MassValue, tank_pressure: PressureValue): + self.chamber_pressure = chamber_pressure.base_value + self.mass_ox = mass_ox.base_value + self.tank_temp = None + self.tank_pressure = tank_pressure.base_value + + def __init__(self, chamber_pressure: PressureValue, mass_ox: MassValue, tank_temp: TemperatureValue): + self.chamber_pressure = chamber_pressure.base_value + self.mass_ox = mass_ox.base_value + self.tank_temp = tank_temp.base_value + self.tank_pressure = None + +class TankState: + def __init__(self, config: SimulationConfig, init_conditions: InitialConditions): + if init_conditions.tank_temp is None: + self.temp = fsolve(lambda T: vapor_pressure(T) - init_conditions.tank_pressure, 273.15)[0] # TODO: Error handling (see RootResults), root method selection + else: + self.temp = init_conditions.tank_temp + self.ox_props = get_NOX_properties(self.temp) + self.mass_ox = init_conditions.mass_ox + self.mass_ox_old = self.mass_ox + self.mdot_ox = 0.0 + self.mdot_vapor = 0.0 + self.pressure = self.ox_props.Pv + + self.mLiq_new = TankState.liquid_mass(config.hardware.tank_volume, self.mass_ox, self.ox_props) + self.mLiq_old = self.mLiq_new + 1.0 + + def liquid_mass(tank_vol: float, mass_ox: float, ox_props: NOXProperties) -> float: + # This can take in standard floats because unit normalization should have already been done... should have. + return ((tank_vol - (mass_ox / ox_props.rho_v)) / + ((1 / ox_props.rho_l) - (1 / ox_props.rho_v))) + +class ChamberState: + AIR_RHO = 1.225 # Density of air (kg/m^3) + + def __init__(self, config: SimulationConfig, init_conditions: InitialConditions): + self.pressure = init_conditions.chamber_pressure + + grain_volume = (d_to_a(config.prop.OD) - d_to_a(config.prop.ID)) * config.prop.length + self.mass_fuel = grain_volume * config.prop.material.rho + self.mass_gas = ChamberState.AIR_RHO * (config.hardware.chamber_volume - grain_volume) + + self.vdot_fuel = 0.0 + self.mdot_fuel = 0.0 + self.mdot_exit = 0.0 + self.rdot = 0.0 + + self.grain_ID = config.prop.ID + self.OF_ratio = config.prop.const_OF_ratio if config.prop.const_OF_ratio is not None else 0.0 + +class MassState: + def __init__(self, config: SimulationConfig, init_conditions: InitialConditions): + self.cg = 0.0 + self.mass_total = 0.0 + +class CombustionState: + def __init__(self): + self.k = 0.0 + self.T = 0.0 + self.M = 0.0 + self.R = 0.0 + self.rho = 0.0 + self.cstar = 0.0 + +class SimulationState: + def __init__(self, config: SimulationConfig, init_conditions: InitialConditions): + self.time = 0.0 + + self.tank = TankState(config, init_conditions) + self.chamber = ChamberState(config, init_conditions) + self.combustion = CombustionState() + self.mass = MassState(config, init_conditions) + self.thrust = 0.0 + self.pressure_drop = 0.0 + + # For interpolating oxidizer pressure curve + self.total_vapor_dP = 0.0 + self.interpolation_timesteps = 1e-10 # Avoids potential divide-by-zero + +### +### SIMULATION OUTPUT +### +class SimulationOutput: + def __init__(self, config: SimulationConfig): + self.time : List[float] = [] + + self.mass_ox : List[MassValue] = [] + self.mdot_ox : List[MassValue] = [] + self.tank_pressure : List[PressureValue] = [] + + self.mass_fuel : List[MassValue] = [] + self.mdot_fuel : List[MassValue] = [] + self.mdot_exit : List[MassValue] = [] + self.rdot : List[LengthValue] = [] + self.chamber_pressure : List[PressureValue] = [] + self.OF_ratio : List[float] = [] + self.grain_ID : List[LengthValue] = [] + + self.pressure_drop : List[PressureValue] = [] + self.thrust : List[MassValue] = [] + + if config.mass_properties is not None: + self.total_mass : List[MassValue] = [] + self.cg : List[LengthValue] = [] + self.include_mass_props = True + else: + self.include_mass_props = False + + self.end_condition : str = "Simulation Unfinished" + + def append_state(self, state: SimulationState): + self.time.append(state.time) + + # TODO: Mass flux / length 'flux' unit? Probably not necessary - denominator is always 'seconds' + self.mass_ox.append(MassValue(state.tank.mass_ox, MassUnit.KILOGRAMS)) + self.mdot_ox.append(MassValue(state.tank.mdot_ox, MassUnit.KILOGRAMS)) + self.tank_pressure.append(PressureValue(state.tank.pressure, PressureUnit.PA)) + + self.mass_fuel.append(MassValue(state.chamber.mass_fuel, MassUnit.KILOGRAMS)) + self.mdot_fuel.append(MassValue(state.chamber.mdot_fuel, MassUnit.KILOGRAMS)) + self.mdot_exit.append(MassValue(state.chamber.mdot_exit, MassUnit.KILOGRAMS)) + self.rdot.append(LengthValue(state.chamber.rdot, LengthUnit.MILLIMETERS)) # Regression is in mm/s by default + self.chamber_pressure.append(PressureValue(state.chamber.pressure, PressureUnit.PA)) + self.OF_ratio.append(state.chamber.OF_ratio) + self.grain_ID.append(LengthValue(state.chamber.grain_ID, LengthUnit.METERS)) + + self.pressure_drop.append(PressureValue(state.pressure_drop, PressureUnit.PA)) + self.thrust.append(MassValue(state.thrust, MassUnit.NEWTONS)) + + if self.include_mass_props: + self.total_mass.append(MassValue(state.mass.mass_total, MassUnit.KILOGRAMS)) + self.cg.append(LengthUnit(state.mass.cg, LengthUnit.METERS)) + + def max_thrust(self): + return MassValue(max([t.base_value for t in self.thrust], MassUnit.NEWTONS)) \ No newline at end of file diff --git a/HRAP - Python/hrap_python/api/sim.py b/HRAP - Python/hrap_python/api/sim.py new file mode 100644 index 0000000..3a288b1 --- /dev/null +++ b/HRAP - Python/hrap_python/api/sim.py @@ -0,0 +1,366 @@ +""" +HRAP Simulation Components (`hrap_python.api.sim`) + +State-transition components for HRAP simulations +""" +from math import pi, sqrt +from util.nox import get_NOX_properties, vapor_pressure +from util.units import d_to_a +from .models import SimulationConfig, SimulationState, VentConfig, TankState +from scipy.interpolate import RegularGridInterpolator +from scipy.optimize import fsolve + +def tank(config: SimulationConfig, state: SimulationState): + """ + Iterates the current tank conditions. + + Parameters + ---------- + config : SimulationConfig + The simulation configuration parameters + + state : SimulationState + The current simulation state + + Returns + ------- + None + """ + dt = config.timestep + + # Find oxidizer thermophysical properties + state.tank.ox_props = get_NOX_properties(state.tank.temp) + + state.tank.pressure = state.tank.ox_props.Pv + + # Find oxidizer mass flow rate + dP = state.tank.pressure - state.chamber.pressure + + if dP < 0: + dP = 0.0 + + state.pressure_drop = dP + + if config.max_burn_time == 0 or state.time <= config.max_burn_time: + if config.hardware.vent_state == VentConfig.NONE: + # No vent - no gas escapes the tank + state.tank.mdot_vapor = 0.0 + if state.tank.mLiq_new == 0: + state.tank.mdot_ox = config.hardware.injector_cda_N*sqrt(2*state.tank.ox_props.rho_v*dP) + else: + state.tank.mdot_ox = config.hardware.injector_cda_N*sqrt(2*state.tank.ox_props.rho_l*dP) + + mass_discharged = (state.tank.mdot_ox+state.tank.mdot_vapor)*dt + elif config.hardware.vent_state == VentConfig.EXTERNAL: + # External vent - gases exit through tank forward closure + state.tank.mdot_vapor = config.hardware.vent_cda*sqrt(2*state.tank.ox_props.rho_v*dP) + if state.tank.mLiq_new == 0: + state.tank.mdot_ox = config.hardware.injector_cda_N*sqrt(2*state.tank.ox_props.rho_v*dP) + else: + state.tank.mdot_ox = config.hardware.injector_cda_N*sqrt(2*state.tank.ox_props.rho_l*dP) + + mass_discharged = (state.tank.mdot_ox+state.tank.mdot_vapor)*dt + elif config.hardware.vent_state == VentConfig.INTERNAL: + # Internal vent - gases enter combustion chamber + state.tank.mdot_vapor = config.vnt_CdA*sqrt(2*state.tank.ox_props.rho_v*dP) + if state.tank.mLiq_new == 0: + state.tank.mdot_ox = config.hardware.injector_cda_N*sqrt(2*state.tank.ox_props.rho_v*dP) + state.tank.mdot_vapor + else: + state.tank.mdot_ox = config.hardware.injector_cda_N*sqrt(2*state.tank.ox_props.rho_l*dP) + state.tank.mdot_vapor + + mass_discharged = state.tank.mdot_ox*dt + else: + print('Error: Vent State Undefined') + exit(-1) + + elif config.max_burn_time > 0 and state.time > config.max_burn_time: + state.tank.mdot_ox = 0 + mass_discharged = 0 + + # Find mass discharged during time step + state.tank.mass_ox_old = state.tank.mass_ox + state.tank.mass_ox = state.tank.mass_ox - state.tank.mdot_ox*dt + + if state.tank.mLiq_new < state.tank.mLiq_old and state.tank.mLiq_new > 0 and state.tank.mdot_ox > 0: + + # Find mass of liquid nitrous evaporated during time step + state.tank.mLiq_old = state.tank.mLiq_new - mass_discharged + state.tank.ox_props = get_NOX_properties(state.tank.temp) + state.tank.mLiq_new = TankState.liquid_mass(config.hardware.tank_volume, state.tank.mass_ox, state.tank.ox_props) + mv = state.tank.mLiq_old - state.tank.mLiq_new + + # Find heat removed from liquid + dT = -mv*state.tank.ox_props.Hv/(state.tank.mLiq_new*state.tank.ox_props.Cp) + state.tank.temp = state.tank.temp + dT + op = get_NOX_properties(state.tank.temp) + + vapor_dP = op.Pv - state.tank.pressure + if vapor_dP < 0: + state.total_vapor_dP += vapor_dP + state.interpolation_timesteps += 1.0 + + elif state.tank.mLiq_new >= state.tank.mLiq_old and state.tank.mLiq_new > 0 and state.tank.mdot_ox > 0: + dP_avg = state.total_vapor_dP / state.interpolation_timesteps + + P_new = state.tank.pressure + dP_avg + + state.tank.temp = fsolve(lambda T: vapor_pressure(T) - P_new, state.tank.temp)[0] # TODO: Error handling + + vapor_dP = state.tank.ox_props.Pv - state.tank.pressure + if vapor_dP < 0: + state.total_vapor_dP += vapor_dP + state.interpolation_timesteps += 1.0 + + state.tank.ox_props = get_NOX_properties(state.tank.temp) + + state.tank.mLiq_new = TankState.liquid_mass(config.hardware.tank_volume, state.tank.mass_ox, state.tank.ox_props) + state.tank.mLiq_old = 0 + + elif state.tank.mLiq_new <= 0 and state.tank.mdot_ox > 0: + if state.tank.mLiq_new != 0: + state.tank.mLiq_new = 0 + + # Find Z factor + Z_old = state.tank.ox_props.Z + + Zguess = Z_old + epsilon = 1.0 + + T_i = state.tank.temp + P_i = state.tank.pressure + + while epsilon >= 0.000001: + T_ratio = ((Zguess * state.tank.mass_ox) / (Z_old * state.tank.mass_ox_old)) ** 0.3 + state.tank.temp = T_ratio * T_i + P_ratio = T_ratio ** (1.3 / 0.3) + state.tank.pressure = P_ratio * P_i + + state.tank.ox_props = get_NOX_properties(state.tank.temp) + + Z = state.tank.ox_props.Z + + epsilon = abs(Zguess - Z) + + Zguess = (Zguess + Z) / 2 + +def shift_OF(config: SimulationConfig, state: SimulationState): + """ + Iterates the propellant regression state assuming a shifting OF-ratio model. + + Parameters + ---------- + config : SimulationConfig + The simulation configuration parameters + + state : SimulationState + The current simulation state + + Returns + ------- + None + """ + dt = config.timestep + + reg_coeff = config.prop.material.regression_coeff + ox_flux = state.tank.mdot_ox / d_to_a(state.chamber.grain_ID) + # Convert regression rate from m/s to mm/s + # Not a fan of the magic number here, but unfortunately the ballistic equation is + # given in units of mm/s. At least it's not leagues per fortnight + state.chamber.rdot = 0.001 * reg_coeff[0] * (ox_flux ** reg_coeff[1]) * (config.prop.length ** reg_coeff[2]) + state.chamber.mdot_fuel = config.prop.material.rho * state.chamber.rdot * pi * state.chamber.grain_ID * config.prop.length + state.chamber.OF_ratio = state.tank.mdot_ox / state.chamber.mdot_fuel + + if state.chamber.mdot_fuel == 0: + state.chamber.OF_ratio = 0 + + new_grain_ID = state.chamber.grain_ID + (2 * state.chamber.rdot * dt) + # TODO: Prefer vdot * rho for mdot calculation? + state.chamber.vdot_fuel = (d_to_a(new_grain_ID) - d_to_a(state.chamber.grain_ID)) * config.prop.length / dt + state.chamber.grain_ID = new_grain_ID + state.chamber.mass_fuel = state.chamber.mass_fuel - (state.chamber.mdot_fuel * dt) + +def const_OF(config: SimulationConfig, state: SimulationState): + """ + Iterates the propellant regression state assuming a constant OF-ratio model. + + Parameters + ---------- + config : SimulationConfig + The simulation configuration parameters + + state : SimulationState + The current simulation state + + Returns + ------- + None + """ + dt = config.timestep + + state.chamber.mdot_fuel = state.tank.mdot_ox / config.prop.const_OF_ratio + state.rdot = state.chamber.mdot_fuel / (config.prop.material.rho * pi * state.chamber.grain_ID * config.prop.length) + + new_grain_ID = state.chamber.grain_ID + (2 * state.rdot * dt) + state.chamber.vdot_fuel = (d_to_a(new_grain_ID) - d_to_a(state.chamber.grain_ID)) * config.prop.length / dt + state.chamber.grain_ID = new_grain_ID + state.chamber.mass_fuel = state.chamber.mass_fuel - (state.chamber.mdot_fuel * dt) + +def combustion(config: SimulationConfig, state: SimulationState): + """ + Iterates the combustion state. + + Parameters + ---------- + config : SimulationConfig + The simulation configuration parameters + + state : SimulationState + The current simulation state + + Returns + ------- + None + """ + if state.time <= config.max_burn_time or config.max_burn_time == 0: + data = config.prop.material + k_interp = RegularGridInterpolator((data.Pc, data.OF), data.k, bounds_error=False, fill_value=None) # TODO: Interpolation method, optimization + k = k_interp((state.chamber.pressure, state.chamber.OF_ratio)) + state.combustion.k = k # Easier to just reference k + M_interp = RegularGridInterpolator((data.Pc, data.OF), data.M, bounds_error=False, fill_value=None) + state.combustion.M = M_interp((state.chamber.pressure, state.chamber.OF_ratio)) + T_interp = RegularGridInterpolator((data.Pc, data.OF), data.T, bounds_error=False, fill_value=None) + state.combustion.T = T_interp((state.chamber.pressure, state.chamber.OF_ratio)) + + state.combustion.R = 8314.5 / state.combustion.M + state.rho = state.chamber.pressure / (state.combustion.R * state.combustion.T) + state.combustion.cstar = config.prop.cstar_eff * sqrt((state.combustion.R * state.combustion.T) / (k * ((2/(k+1)) ** ((k+1)/(k-1))))) + +def chamber(config: SimulationConfig, state: SimulationState): + """ + Iterates the chamber state (mass flux, pressure change, etc.). + + Parameters + ---------- + config : SimulationConfig + The simulation configuration parameters + + state : SimulationState + The current simulation state + + Returns + ------- + None + """ + dt = config.timestep + + if config.hardware.chamber_volume == 0: + V = d_to_a(state.chamber.grain_ID) * config.prop.length + else: + V = config.hardware.chamber_volume - (d_to_a(config.prop.OD) - d_to_a(state.chamber.grain_ID)) * config.prop.length + + state.chamber.mdot_exit = state.chamber.pressure * config.nozzle.Cd * d_to_a(config.nozzle.throat) / state.combustion.cstar + + dm_g = state.chamber.mdot_fuel + state.tank.mdot_ox - state.chamber.mdot_exit + + if state.tank.mdot_ox == 0: + state.dm_g = -1 * state.chamber.mdot_exit + + state.chamber.mass_gas = state.chamber.mass_gas + (dm_g * dt) + + dP = state.chamber.pressure * ((dm_g / state.chamber.mass_gas) - state.chamber.vdot_fuel/V) + + state.chamber.pressure = state.chamber.pressure + (dP * dt) + + if state.chamber.pressure <= config.ambient_pressure: + state.chamber.pressure = config.ambient_pressure + state.chamber.mdot_exit = 0 + +def nozzle(config: SimulationConfig, state: SimulationState): + """ + Iterates the nozzle state - the final state transition in a typical simulation iteration. + + Parameters + ---------- + config : SimulationConfig + The simulation configuration parameters + + state : SimulationState + The current simulation state + + Returns + ------- + None + """ + if state.chamber.pressure > config.ambient_pressure: + k = state.combustion.k # Simpler to just reference k + A_Ratio = lambda M: ( + ((((k+1)/2) ** (-(k+1)/(2*(k-1)))) * + ((1+(k-1)/2*(M**2)) ** ((k+1)/(2*(k-1)))) / M) + - config.nozzle.exp_ratio + ) + M = fsolve(A_Ratio, 3.0)[0] # TODO: Error handling + Pe = state.chamber.pressure * ((1 + 0.5*(k-1)*(M**2)) ** (-1*k/(k-1))) + Cf = ( + sqrt(((2*(k**2))/(k-1)) * + ((2/(k+1)) ** ((k+1)/(k-1))) * + (1-((Pe/state.chamber.pressure) ** ((k-1)/k)))) + + ((Pe-config.ambient_pressure)*(d_to_a(config.nozzle.throat)*config.nozzle.exp_ratio)) / + (state.chamber.pressure*d_to_a(config.nozzle.throat)) + ) + state.thrust = config.nozzle.efficiency * Cf * d_to_a(config.nozzle.throat) * state.chamber.pressure * config.nozzle.Cd + + if state.thrust < 0: + state.thrust = 0 + + else: + state.thrust = 0 + +def mass(config: SimulationConfig, state: SimulationState): + """ + Iterates the mass properties of a state - optional, only when specified. + + Parameters + ---------- + config : SimulationConfig + The simulation configuration parameters + + state : SimulationState + The current simulation state + + Returns + ------- + None + """ + state.mass.mass_total = config.mass_properties.motor_mass + state.tank.mass_ox + state.chamber.mass_fuel + + if state.tank.mLiq_new < 0: + state.tank.mLiq_new = 0 + + tA = d_to_a(config.tank_diameter) + + if state.tank.mLiq_new > 0: + m_v = state.tank.mass_ox - state.tank.mLiq_new + + vl = state.tank.mLiq_new/state.tank.ox_props.rho_l + vv = config.tnk_V - vl + + hl = vl/tA + hv = vv/tA + + CoMl = config.tnk_X - (hl / 2) + CoMv = config.tnk_X - hl - (hv / 2) + CoMf = config.cmbr_X - (config.prop.length / 2) + + state.mass.cg = (state.tank.mLiq_new*CoMl + m_v*CoMv + state.chamber.mass_fuel*CoMf + config.mass_properties.motor_mass*config.mass_properties.motor_cg) / state.mass.mass_total + + elif state.tank.mLiq_new == 0: + m_v = state.tank.mass_ox + + vv = config.tnk_V + + hv = vv / tA + + CoMv = config.tnk_X - (hv / 2) + CoMf = config.cmbr_X - (config.prop.length / 2) + + state.mass.cg = (m_v*CoMv + state.chamber.mass_fuel*CoMf + config.mass_properties.motor_mass*config.mass_properties.motor_cg) / state.mass.mass_total diff --git a/HRAP - Python/hrap_python/appcode.m b/HRAP - Python/hrap_python/appcode.m new file mode 100644 index 0000000..eb4c9c4 --- /dev/null +++ b/HRAP - Python/hrap_python/appcode.m @@ -0,0 +1,2802 @@ +classdef HRAP < matlab.apps.AppBase + + % Properties that correspond to app components + properties (Access = public) + HRAP_v2022_06_04 matlab.ui.Figure + ReleaseLabel matlab.ui.control.Label + AuthorLabel matlab.ui.control.Label + AppTabs matlab.ui.container.TabGroup + MotorTab matlab.ui.container.Tab + SaveMotor matlab.ui.control.Button + LoadMotor matlab.ui.control.Button + RunSimulation matlab.ui.control.Button + InitialConditionsPanel matlab.ui.container.Panel + m_O_unit matlab.ui.control.DropDown + Pa_unit matlab.ui.control.DropDown + P_cmbr_unit matlab.ui.control.DropDown + T_tnk_unit matlab.ui.control.DropDown + OxidizerDropDown matlab.ui.control.DropDown + TankDropDown matlab.ui.control.DropDown + AmbientPressure matlab.ui.control.NumericEditField + AmbientPressureEditField_2Label matlab.ui.control.Label + OxidizerFill matlab.ui.control.NumericEditField + ChamberPressure matlab.ui.control.NumericEditField + StartingChamberPressureEditFieldLabel matlab.ui.control.Label + TankCond matlab.ui.control.NumericEditField + SimulationConfigurationPanel matlab.ui.container.Panel + timestep_label matlab.ui.control.Label + burntime_label matlab.ui.control.Label + runtime_label matlab.ui.control.Label + RegressionModel matlab.ui.control.DropDown + RegressionModelDropDownLabel matlab.ui.control.Label + Timestep matlab.ui.control.NumericEditField + SimulationTimestepEditFieldLabel matlab.ui.control.Label + BurnTime matlab.ui.control.NumericEditField + MaxBurnTimeEditFieldLabel matlab.ui.control.Label + RunTime matlab.ui.control.NumericEditField + MaxSimulationRunTimeEditFieldLabel matlab.ui.control.Label + MassPropertiesPanel matlab.ui.container.Panel + mtr_m_unit matlab.ui.control.DropDown + mtr_cg_unit matlab.ui.control.DropDown + cmbr_X_unit matlab.ui.control.DropDown + tnk_X_unit matlab.ui.control.DropDown + MassProperties matlab.ui.control.CheckBox + MotorMass matlab.ui.control.NumericEditField + EmptyMotorMassEditFieldLabel matlab.ui.control.Label + MotorCG matlab.ui.control.NumericEditField + EmptyMotorCenterofMassEditFieldLabel matlab.ui.control.Label + GrainLocation matlab.ui.control.NumericEditField + FuelGrainLocationEditFieldLabel matlab.ui.control.Label + TankLocation matlab.ui.control.NumericEditField + OxidizerTankLocationEditFieldLabel matlab.ui.control.Label + InjectorConfigurationPanel matlab.ui.container.Panel + inj_D_unit matlab.ui.control.DropDown + vnt_D_unit matlab.ui.control.DropDown + VentState matlab.ui.control.DropDown + VentStateDropDownLabel matlab.ui.control.Label + VentCd matlab.ui.control.NumericEditField + VentDischargeCoefficientEditFieldLabel matlab.ui.control.Label + VentDiameter matlab.ui.control.NumericEditField + VentDiameterEditFieldLabel matlab.ui.control.Label + NumberofInjectors matlab.ui.control.NumericEditField + NumberofInjectorsEditFieldLabel matlab.ui.control.Label + InjectorCd matlab.ui.control.NumericEditField + InjectorDischargeCoefficientEditFieldLabel matlab.ui.control.Label + InjectorDiameter matlab.ui.control.NumericEditField + InjectorDiameterEditFieldLabel matlab.ui.control.Label + PropellantConfigurationPanel matlab.ui.container.Panel + m_unit matlab.ui.control.Label + n_unit matlab.ui.control.Label + a_unit matlab.ui.control.Label + grnL_unit matlab.ui.control.DropDown + grnOD_unit matlab.ui.control.DropDown + grnID_unit matlab.ui.control.DropDown + density_unit matlab.ui.control.DropDown + cstar_percent matlab.ui.control.Label + ConstantOF matlab.ui.control.NumericEditField + ConstantOFRatioLabel matlab.ui.control.Label + LoadPropellantConfig matlab.ui.control.Button + GrainLength matlab.ui.control.NumericEditField + GrainLengthEditFieldLabel matlab.ui.control.Label + GrainOD matlab.ui.control.NumericEditField + GrainODEditFieldLabel matlab.ui.control.Label + GrainID matlab.ui.control.NumericEditField + GrainIDEditFieldLabel matlab.ui.control.Label + CEfficiency matlab.ui.control.NumericEditField + CEfficiencyEditFieldLabel matlab.ui.control.Label + LengthExponent matlab.ui.control.NumericEditField + LengthExponentmEditFieldLabel matlab.ui.control.Label + RegressionExponent matlab.ui.control.NumericEditField + RegressionExponentnEditFieldLabel matlab.ui.control.Label + RegressionCoefficient matlab.ui.control.NumericEditField + RegressionCoefficientaEditFieldLabel matlab.ui.control.Label + PropellantDensity matlab.ui.control.NumericEditField + PropellantDensityEditFieldLabel matlab.ui.control.Label + PropellantName matlab.ui.control.EditField + PropellantNameEditFieldLabel matlab.ui.control.Label + NozzleConfigurationPanel matlab.ui.container.Panel + NozzleDropDown matlab.ui.control.DropDown + noz_ext_unit matlab.ui.control.DropDown + noz_percent matlab.ui.control.Label + noz_thrt_unit matlab.ui.control.DropDown + NozzleCd matlab.ui.control.NumericEditField + NozzleDischargeCoefficientEditFieldLabel matlab.ui.control.Label + NozzleEfficiency matlab.ui.control.NumericEditField + NozzleEfficiencyEditFieldLabel matlab.ui.control.Label + NozzleExit matlab.ui.control.NumericEditField + ThroatDiameter matlab.ui.control.NumericEditField + NozzleThroatDiameterEditFieldLabel matlab.ui.control.Label + TankDimensionsPanel matlab.ui.container.Panel + tnk_V_unit matlab.ui.control.DropDown + cmbr_V_unit matlab.ui.control.DropDown + tnk_D_unit matlab.ui.control.DropDown + tnk_L_unit matlab.ui.control.DropDown + ChamberVolumeByDimensions matlab.ui.control.CheckBox + ChamberVolume matlab.ui.control.NumericEditField + CombustionChamberVolumeEditFieldLabel matlab.ui.control.Label + TankVolume matlab.ui.control.NumericEditField + OxidizerTankVolumeEditFieldLabel matlab.ui.control.Label + TankDiameter matlab.ui.control.NumericEditField + OxidizerTankDiameterEditFieldLabel matlab.ui.control.Label + TankLength matlab.ui.control.NumericEditField + OxidizerTankLengthEditFieldLabel matlab.ui.control.Label + TankVolumeByDimensions matlab.ui.control.CheckBox + MotorConfiguration matlab.ui.control.EditField + ConfigurationNameLabel matlab.ui.control.Label + ResultsTab matlab.ui.container.Tab + Legend matlab.ui.control.EditField + LegendEditFieldLabel matlab.ui.control.Label + YaxisUnits matlab.ui.control.DropDown + YaxisUnitsDropDownLabel matlab.ui.control.Label + XaxisUnits matlab.ui.control.DropDown + XaxisUnitsDropDownLabel matlab.ui.control.Label + ExportCSV matlab.ui.control.Button + ExportRSE matlab.ui.control.Button + SaveResults matlab.ui.control.Button + SavePlot matlab.ui.control.Button + ClearPlot matlab.ui.control.Button + AddPlot matlab.ui.control.Button + PerformanceSummary matlab.ui.control.TextArea + PerformanceSummaryTextAreaLabel matlab.ui.control.Label + Yaxis matlab.ui.control.DropDown + YaxisDropDownLabel matlab.ui.control.Label + PlotTitle matlab.ui.control.EditField + PlotTitleEditFieldLabel matlab.ui.control.Label + Xaxis matlab.ui.control.DropDown + XaxisDropDownLabel matlab.ui.control.Label + UIAxes matlab.ui.control.UIAxes + AboutTab matlab.ui.container.Tab + Image2 matlab.ui.control.Image + Label matlab.ui.control.Label + Image matlab.ui.control.Image + ClickhereformoreinformationregardingHRAPitsusageandButton matlab.ui.control.Button + TextArea matlab.ui.control.TextArea + AppTitle matlab.ui.control.Label + end + + + properties (Access = public) + s = struct(); % input structure + o = struct(); % output structure + x = struct(); % state structure + u = struct(); % selected units structure + plt = struct(); % plotting structure + appDir % user install location + t % time variable + end + + + % Callbacks that handle component events + methods (Access = private) + + % Code that executes after component creation + function startupFcn(app) + movegui(app.HRAP_v2022_06_04, 'center') + [~, result] = system('path'); + app.appDir = convertCharsToStrings(char(regexpi(result, 'Path=(.*?);', 'tokens', 'once'))); + end + + % Button pushed function: LoadPropellantConfig + function load_propellant(app, event) + [file,path,~] = uigetfile('*.mat','Select a propellant file',sprintf('%s\\propellant_configs',app.appDir)); + + if isequal(file,0) + else + load(fullfile(path, file)); %#ok + app.s.prop_file = convertCharsToStrings(fullfile(path,file)); + app.s.prop_nm = s.prop_nm; %#ok<*ADPROPLC> + app.s.prop_k = s.prop_k; + app.s.prop_M = s.prop_M; + app.s.prop_OF = s.prop_OF; + app.s.prop_Pc = s.prop_Pc; + app.s.prop_Reg = s.prop_Reg; + app.s.prop_Rho = s.prop_Rho; + app.s.prop_T = s.prop_T; + app.s.opt_OF = s.opt_OF; + + app.PropellantName.Value = app.s.prop_nm; + app.PropellantDensity.Value = app.s.prop_Rho; + app.RegressionCoefficient.Value = app.s.prop_Reg(1); + app.RegressionExponent.Value = app.s.prop_Reg(2); + app.LengthExponent.Value = app.s.prop_Reg(3); + app.ConstantOF.Value = app.s.opt_OF; + end + end + + % Value changed function: MassProperties + function enable_mass_properties(app, event) + value = app.MassProperties.Value; + + if value == 1 + app.TankLocation.Enable = 1; + app.TankLocation.Editable = 1; + app.OxidizerTankLocationEditFieldLabel.Enable = 1; + app.tnk_X_unit.Enable = 1; + app.tnk_X_unit.Editable = 1; + app.GrainLocation.Enable = 1; + app.GrainLocation.Editable = 1; + app.FuelGrainLocationEditFieldLabel.Enable = 1; + app.cmbr_X_unit.Enable = 1; + app.cmbr_X_unit.Editable = 1; + app.MotorCG.Enable = 1; + app.MotorCG.Editable = 1; + app.EmptyMotorCenterofMassEditFieldLabel.Enable = 1; + app.mtr_cg_unit.Enable = 1; + app.mtr_cg_unit.Editable = 1; + app.MotorMass.Enable = 1; + app.MotorMass.Editable = 1; + app.EmptyMotorMassEditFieldLabel.Enable = 1; + app.mtr_m_unit.Enable = 1; + app.mtr_m_unit.Editable = 1; + if app.TankVolumeByDimensions.Value == 0 + app.TankDiameter.Enable = 1; + app.TankDiameter.Editable = 1; + app.OxidizerTankDiameterEditFieldLabel.Enable = 1; + app.tnk_D_unit.Enable = 1; + app.tnk_D_unit.Editable = 1; + end + else + app.TankLocation.Enable = 0; + app.TankLocation.Editable = 0; + app.OxidizerTankLocationEditFieldLabel.Enable = 0; + app.tnk_X_unit.Enable = 0; + app.tnk_X_unit.Editable = 0; + app.GrainLocation.Enable = 0; + app.GrainLocation.Editable = 0; + app.FuelGrainLocationEditFieldLabel.Enable = 0; + app.cmbr_X_unit.Enable = 0; + app.cmbr_X_unit.Editable = 0; + app.MotorCG.Enable = 0; + app.MotorCG.Editable = 0; + app.EmptyMotorCenterofMassEditFieldLabel.Enable = 0; + app.mtr_cg_unit.Enable = 0; + app.mtr_cg_unit.Editable = 0; + app.MotorMass.Enable = 0; + app.MotorMass.Editable = 0; + app.EmptyMotorMassEditFieldLabel.Enable = 0; + app.mtr_m_unit.Enable = 0; + app.mtr_m_unit.Editable = 0; + if app.TankVolumeByDimensions.Value == 0 + app.TankDiameter.Enable = 0; + app.TankDiameter.Editable = 0; + app.OxidizerTankDiameterEditFieldLabel.Enable = 0; + app.tnk_D_unit.Enable = 0; + app.tnk_D_unit.Editable = 0; + end + end + end + + % Value changed function: TankVolumeByDimensions + function tank_dimensions(app, event) + value = app.TankVolumeByDimensions.Value; + + if value == 1 + app.TankLength.Enable = 1; + app.TankLength.Editable = 1; + app.OxidizerTankLengthEditFieldLabel.Enable = 1; + app.tnk_L_unit.Enable = 1; + app.tnk_L_unit.Editable = 1; + if app.MassProperties.Value == 0 + app.TankDiameter.Enable = 1; + app.TankDiameter.Editable = 1; + app.OxidizerTankDiameterEditFieldLabel.Enable = 1; + app.tnk_D_unit.Enable = 1; + app.tnk_D_unit.Editable = 1; + end + app.TankVolume.Enable = 0; + app.TankVolume.Editable = 0; + app.OxidizerTankVolumeEditFieldLabel.Enable = 0; + app.tnk_V_unit.Enable = 0; + app.tnk_V_unit.Editable = 0; + else + app.TankLength.Enable = 0; + app.TankLength.Editable = 0; + app.OxidizerTankLengthEditFieldLabel.Enable = 0; + app.tnk_L_unit.Enable = 0; + app.tnk_L_unit.Editable = 0; + if app.MassProperties.Value == 0 + app.TankDiameter.Enable = 0; + app.TankDiameter.Editable = 0; + app.OxidizerTankDiameterEditFieldLabel.Enable = 0; + app.tnk_D_unit.Enable = 0; + app.tnk_D_unit.Editable = 0; + end + app.TankVolume.Enable = 1; + app.TankVolume.Editable = 1; + app.OxidizerTankVolumeEditFieldLabel.Enable = 1; + app.tnk_V_unit.Enable = 1; + app.tnk_V_unit.Editable = 1; + end + end + + % Value changed function: ChamberVolumeByDimensions + function combustion_chamber_volume(app, event) + value = app.ChamberVolumeByDimensions.Value; + + if value == 0 + app.ChamberVolume.Enable = 1; + app.ChamberVolume.Editable = 1; + app.CombustionChamberVolumeEditFieldLabel.Enable = 1; + app.cmbr_V_unit.Enable = 1; + app.cmbr_V_unit.Editable = 1; + else + app.ChamberVolume.Enable = 0; + app.ChamberVolume.Editable = 0; + app.CombustionChamberVolumeEditFieldLabel.Enable = 0; + app.cmbr_V_unit.Enable = 0; + app.cmbr_V_unit.Editable = 0; + end + end + + % Value changed function: RegressionModel + function reg_model(app, event) + value = app.RegressionModel.Value; + + if value == "Shifting OF" + app.RegressionCoefficient.Enable = 1; + app.RegressionCoefficient.Editable = 1; + app.RegressionCoefficientaEditFieldLabel.Enable = 1; + app.a_unit.Enable = 1; + app.RegressionExponent.Enable = 1; + app.RegressionExponent.Editable = 1; + app.RegressionExponentnEditFieldLabel.Enable = 1; + app.n_unit.Enable = 1; + app.LengthExponent.Enable = 1; + app.LengthExponent.Editable = 1; + app.LengthExponentmEditFieldLabel.Enable = 1; + app.m_unit.Enable = 1; + app.ConstantOF.Enable = 0; + app.ConstantOF.Editable = 0; + app.ConstantOFRatioLabel.Enable = 0; + else + app.RegressionCoefficient.Enable = 0; + app.RegressionCoefficient.Editable = 0; + app.RegressionCoefficientaEditFieldLabel.Enable = 0; + app.a_unit.Enable = 0; + app.RegressionExponent.Enable = 0; + app.RegressionExponent.Editable = 0; + app.RegressionExponentnEditFieldLabel.Enable = 0; + app.n_unit.Enable = 0; + app.LengthExponent.Enable = 0; + app.LengthExponent.Editable = 0; + app.LengthExponentmEditFieldLabel.Enable = 0; + app.m_unit.Enable = 0; + app.ConstantOF.Enable = 1; + app.ConstantOF.Editable = 1; + app.ConstantOFRatioLabel.Enable = 1; + end + end + + % Value changed function: NozzleDropDown + function noz_definition(app, event) + value = app.NozzleDropDown.Value; + if value == "Nozzle Expansion Ratio" + app.noz_ext_unit.Enable = 0; + app.noz_ext_unit.Editable = 0; + app.noz_ext_unit.Visible = 0; + else + app.noz_ext_unit.Enable = 1; + app.noz_ext_unit.Editable = 1; + app.noz_ext_unit.Visible = 1; + end + end + + % Value changed function: TankDropDown + function tank_condition(app, event) + value = app.TankDropDown.Value; + if value == "Starting Tank Temperature" + app.T_tnk_unit.Items = {'F','C','K','R'}; + else + app.T_tnk_unit.Items = {'psi','psf','atm','Pa','kPa','Bar','MPa'}; + end + end + + % Value changed function: OxidizerDropDown + function tank_fill(app, event) + value = app.OxidizerDropDown.Value; + if value == "Starting Oxidizer Mass" + app.m_O_unit.Items = {'lbm','kg','g','oz'}; + else + app.m_O_unit.Items = {'%'}; + end + end + + % Value changed function: VentState + function vent_state(app, event) + value = app.VentState.Value; + if value == "None" + app.VentCd.Enable = 0; + app.VentCd.Editable = 0; + app.VentDischargeCoefficientEditFieldLabel.Enable = 0; + app.VentDiameter.Enable = 0; + app.VentDiameter.Editable = 0; + app.VentDiameterEditFieldLabel.Enable = 0; + app.vnt_D_unit.Enable = 0; + app.vnt_D_unit.Editable = 0; + else + app.VentCd.Enable = 1; + app.VentCd.Editable = 1; + app.VentDischargeCoefficientEditFieldLabel.Enable = 1; + app.VentDiameter.Enable = 1; + app.VentDiameter.Editable = 1; + app.VentDiameterEditFieldLabel.Enable = 1; + app.vnt_D_unit.Enable = 1; + app.vnt_D_unit.Editable = 0; + end + end + + % Button pushed function: RunSimulation + function run_sim(app, event) + app.RunSimulation.BackgroundColor = [0.8,0.8,0.8]; + + % Pull in units + + value = app.tnk_V_unit.Value; + + if value == "in^3" + app.u.tnk_V = 0.0254^3; + elseif value == "ft^3" + app.u.tnk_V = 0.3048^3; + elseif value == "cm^3" + app.u.tnk_V = 0.01^3; + elseif value == "L" + app.u.tnk_V = 0.001; + elseif value == "Gal" + app.u.tnk_V = 0.00378541; + elseif value == "m^3" + app.u.tnk_V = 1; + end + + value = app.tnk_L_unit.Value; + + if value == "in" + app.u.tnk_L = 0.0254; + elseif value == "ft" + app.u.tnk_L = 0.3048; + elseif value == "cm" + app.u.tnk_L = 0.01; + elseif value == "mm" + app.u.tnk_L = 0.001; + elseif value == "m" + app.u.tnk_L = 1; + end + + value = app.tnk_D_unit.Value; + + if value == "in" + app.u.tnk_D = 0.0254; + elseif value == "ft" + app.u.tnk_D = 0.3048; + elseif value == "cm" + app.u.tnk_D = 0.01; + elseif value == "mm" + app.u.tnk_D = 0.001; + elseif value == "m" + app.u.tnk_D = 1; + end + + value = app.cmbr_V_unit.Value; + + if value == "in^3" + app.u.cmbr_V = 0.0254^3; + elseif value == "ft^3" + app.u.cmbr_V = 0.3048^3; + elseif value == "cm^3" + app.u.cmbr_V = 0.01^3; + elseif value == "L" + app.u.cmbr_V = 0.001; + elseif value == "Gal" + app.u.cmbr_V = 0.00378541; + elseif value == "m^3" + app.u.cmbr_V = 1; + end + + value = app.noz_thrt_unit.Value; + + if value == "in" + app.u.noz_thrt = 0.0254; + elseif value == "ft" + app.u.noz_thrt = 0.3048; + elseif value == "cm" + app.u.noz_thrt = 0.01; + elseif value == "mm" + app.u.noz_thrt = 0.001; + elseif value == "m" + app.u.noz_thrt = 1; + end + + value = app.noz_ext_unit.Value; + + if value == "in" + app.u.noz_ext = 0.0254; + elseif value == "ft" + app.u.noz_ext = 0.3048; + elseif value == "cm" + app.u.noz_ext = 0.01; + elseif value == "mm" + app.u.noz_ext = 0.001; + elseif value == "m" + app.u.noz_ext = 1; + end + + value = app.tnk_X_unit.Value; + + if value == "in" + app.u.tnk_X = 0.0254; + elseif value == "ft" + app.u.tnk_X = 0.3048; + elseif value == "cm" + app.u.tnk_X = 0.01; + elseif value == "mm" + app.u.tnk_X = 0.001; + elseif value == "m" + app.u.tnk_X = 1; + end + + value = app.cmbr_X_unit.Value; + + if value == "in" + app.u.cmbr_X = 0.0254; + elseif value == "ft" + app.u.cmbr_X = 0.3048; + elseif value == "cm" + app.u.cmbr_X = 0.01; + elseif value == "mm" + app.u.cmbr_X = 0.001; + elseif value == "m" + app.u.cmbr_X = 1; + end + + value = app.mtr_cg_unit.Value; + + if value == "in" + app.u.mtr_cg = 0.0254; + elseif value == "ft" + app.u.mtr_cg = 0.3048; + elseif value == "cm" + app.u.mtr_cg = 0.01; + elseif value == "mm" + app.u.mtr_cg = 0.001; + elseif value == "m" + app.u.mtr_cg = 1; + end + + value = app.mtr_m_unit.Value; + + if value == "lbm" + app.u.mtr_m = 0.453592; + elseif value == "oz" + app.u.mtr_m = 0.0283495; + elseif value == "g" + app.u.mtr_m = 0.001; + elseif value == "kg" + app.u.mtr_m = 1; + end + + value = app.P_cmbr_unit.Value; + + if value == "psi" + app.u.P_cmbr = 101325/14.696; + elseif value == "psf" + app.u.P_cmbr = 101325/14.696*144; + elseif value == "atm" + app.u.P_cmbr = 101325; + elseif value == "MPa" + app.u.P_cmbr = 1000000; + elseif value == "kPa" + app.u.P_cmbr = 1000; + elseif value == "Bar" + app.u.P_cmbr = 100000; + elseif value == "Pa" + app.u.P_cmbr = 1; + end + + value = app.Pa_unit.Value; + + if value == "psi" + app.u.Pa = 101325/14.696; + elseif value == "psf" + app.u.Pa = 101325/14.696*144; + elseif value == "atm" + app.u.Pa = 101325; + elseif value == "MPa" + app.u.Pa = 1000000; + elseif value == "kPa" + app.u.Pa = 1000; + elseif value == "Bar" + app.u.Pa = 100000; + elseif value == "Pa" + app.u.Pa = 1; + end + + value = app.density_unit.Value; + + if value == "lb/in^3" + app.u.prop_Rho = 1/(2.205*0.0254^3); + elseif value == "lb/ft^3" + app.u.prop_Rho = 1/(2.205*0.3048^3); + elseif value == "g/cm^3" + app.u.prop_Rho = 1000; + elseif value == "kg/m^3" + app.u.prop_Rho = 1; + end + + value = app.inj_D_unit.Value; + + if value == "in" + app.u.inj_D = 0.0254; + elseif value == "ft" + app.u.inj_D = 0.3048; + elseif value == "cm" + app.u.inj_D = 0.01; + elseif value == "mm" + app.u.inj_D = 0.001; + elseif value == "m" + app.u.inj_D = 1; + end + + value = app.vnt_D_unit.Value; + + if value == "in" + app.u.vnt_D = 0.0254; + elseif value == "ft" + app.u.vnt_D = 0.3048; + elseif value == "cm" + app.u.vnt_D = 0.01; + elseif value == "mm" + app.u.vnt_D = 0.001; + elseif value == "m" + app.u.vnt_D = 1; + end + + value = app.grnID_unit.Value; + + if value == "in" + app.u.grn_ID = 0.0254; + elseif value == "ft" + app.u.grn_ID = 0.3048; + elseif value == "cm" + app.u.grn_ID = 0.01; + elseif value == "mm" + app.u.grn_ID = 0.001; + elseif value == "m" + app.u.grn_ID = 1; + end + + value = app.grnOD_unit.Value; + + if value == "in" + app.u.grn_OD = 0.0254; + elseif value == "ft" + app.u.grn_OD = 0.3048; + elseif value == "cm" + app.u.grn_OD = 0.01; + elseif value == "mm" + app.u.grn_OD = 0.001; + elseif value == "m" + app.u.grn_OD = 1; + end + + value = app.grnL_unit.Value; + + if value == "in" + app.u.grn_L = 0.0254; + elseif value == "ft" + app.u.grn_L = 0.3048; + elseif value == "cm" + app.u.grn_L = 0.01; + elseif value == "mm" + app.u.grn_L = 0.001; + elseif value == "kg" + app.u.grn_L = 1; + end + + value = app.m_O_unit.Value; + + if value == "lbm" + app.u.m_o = 0.453592; + elseif value == "oz" + app.u.m_o = 0.0283495; + elseif value == "g" + app.u.m_o = 0.001; + elseif value == "kg" + app.u.m_o = 1; + end + + % initialize variables for HRAP + + app.s.grn_OD = app.GrainOD.Value*app.u.grn_OD; + app.s.grn_ID = app.GrainID.Value*app.u.grn_ID; + app.s.grn_L = app.GrainLength.Value*app.u.grn_L; + app.s.cstar_eff = app.CEfficiency.Value/100; + if app.RegressionModel.Value == "Constant OF" + app.s.const_OF = app.ConstantOF.Value; + end + app.s.mtr_nm = app.MotorConfiguration.Value; + app.s.tmax = app.RunTime.Value; + app.s.tburn = app.BurnTime.Value; + app.s.dt = app.Timestep.Value; + app.s.Pa = app.AmbientPressure.Value*app.u.Pa; + if app.MassProperties.Value == 1 || app.TankVolumeByDimensions.Value == 1 + app.s.tnk_D = app.TankDiameter.Value*app.u.tnk_D; + end + if app.TankVolumeByDimensions.Value == 1 + app.s.tnk_V = app.TankLength.Value*app.u.tnk_L*0.25*pi*(app.TankDiameter.Value*app.u.tnk_D)^2; + else + app.s.tnk_V = app.TankVolume.Value*app.u.tnk_V; + end + if app.ChamberVolumeByDimensions.Value == 1 + app.s.cmbr_V = app.GrainLength.Value*app.u.grn_L*0.25*pi*(app.GrainOD.Value*app.u.grn_OD)^2; + else + app.s.cmbr_V = app.ChamberVolume.Value*app.u.cmbr_V; + end + if app.RegressionModel.Value == "Constant OF" + app.s.regression_model = @(s,x) const_OF(s,x); + elseif app.RegressionModel.Value == "Shifting OF" + app.s.regression_model = @(s,x) shift_OF(s,x); + app.s.prop_Reg(1) = app.RegressionCoefficient.Value; + app.s.prop_Reg(2) = app.RegressionExponent.Value; + app.s.prop_Reg(3) = app.LengthExponent.Value; + end + app.s.noz_Cd = app.NozzleCd.Value; + app.s.noz_thrt = app.ThroatDiameter.Value*app.u.noz_thrt; + if app.NozzleDropDown.Value == "Nozzle Expansion Ratio" + app.s.noz_ER = app.NozzleExit.Value; + elseif app.NozzleDropDown.Value == "Nozzle Exit Diameter" + app.s.noz_ER = (app.NozzleExit.Value*app.u.noz_ext)^2/(app.s.noz_thrt)^2; + end + app.s.noz_eff = app.NozzleEfficiency.Value/100; + app.s.inj_CdA = 0.25*pi*(app.InjectorDiameter.Value*app.u.inj_D)^2*app.InjectorCd.Value; + app.s.inj_N = app.NumberofInjectors.Value; + if app.VentState.Value == "None" + app.s.vnt_S = 0; + elseif app.VentState.Value == "External" + app.s.vnt_S = 1; + app.s.vnt_CdA = 0.25*pi*(app.VentDiameter.Value*app.u.vnt_D)^2*app.VentCd.Value; + else + app.s.vnt_S = 2; + app.s.vnt_CdA = 0.25*pi*(app.VentDiameter.Value*app.u.vnt_D)^2*app.VentCd.Value; + end + if app.MassProperties.Value == 1 + app.s.mtr_m = app.MotorMass.Value*app.u.mtr_m; + app.s.mtr_cg = app.MotorCG.Value*app.u.mtr_cg; + app.s.tnk_X = app.TankLocation.Value*app.u.tnk_X; + app.s.cmbr_X = app.GrainLocation.Value*app.u.cmbr_X; + app.s.mp_calc = 1; + else + app.s.mp_calc = 0; + end + if app.TankDropDown.Value == "Starting Tank Temperature" + value = app.T_tnk_unit.Value; + if value == "C" + app.x.T_tnk = app.TankCond.Value+273.15; + elseif value == "R" + app.x.T_tnk = app.TankCond.Value/1.8; + elseif value == "F" + app.x.T_tnk = ((app.TankCond.Value-32)/1.8)+273.15; + else + app.x.T_tnk = app.TankCond.Value; + end + elseif app.TankDropDown.Value == "Starting Tank Pressure" + value = app.T_tnk_unit.Value; + if value == "psi" + app.u.T_tnk = 101325/14.696; + elseif value == "psf" + app.u.T_tnk = 101325/14.696*144; + elseif value == "atm" + app.u.T_tnk = 101325; + elseif value == "MPa" + app.u.T_tnk = 1000000; + elseif value == "kPa" + app.u.T_tnk = 1000; + elseif value == "Bar" + app.u.T_tnk = 100000; + elseif value == "Pa" + app.u.T_tnk = 1; + end + a1 = -6.71893; + a2 = 1.35966; + a3 = -1.3779; + a4 = -4.051; + Pc = 7251000; + Tc = 309.57; + Pv = @(T) Pc*exp((1/(T/Tc))*(a1*(1-T/Tc) + a2*(1-(T/Tc))^(3/2) + a3*(1-(T/Tc))^(5/2) + a4*(1-(T/Tc))^5)) - app.TankCond.Value*app.u.T_tnk; + app.x.T_tnk = fzero(Pv,273.15); + end + + app.x.ox_props = NOX(app.x.T_tnk); + + if app.OxidizerDropDown.Value == "Tank Fill Percentage" + app.x.m_o = (app.OxidizerFill.Value/100)*app.s.tnk_V*app.x.ox_props.rho_l + (1-app.OxidizerFill.Value/100)*app.s.tnk_V*app.x.ox_props.rho_v; + else + app.x.m_o = app.OxidizerFill.Value*app.u.m_o; + end + app.x.P_tnk = app.x.ox_props.Pv; + app.x.P_cmbr = app.ChamberPressure.Value*app.u.P_cmbr; + app.x.mdot_o = 0; + app.x.mLiq_new = (app.s.tnk_V - (app.x.m_o/app.x.ox_props.rho_v))/((1/app.x.ox_props.rho_l)-(1/app.x.ox_props.rho_v)); + app.x.mLiq_old = app.x.mLiq_new + 1; + app.x.m_f = 0.25*pi*(app.s.grn_OD^2 - app.s.grn_ID^2)*app.s.prop_Rho*app.s.grn_L; + app.x.m_g = 1.225*(app.s.cmbr_V - 0.25*pi*(app.s.grn_OD^2 - app.s.grn_ID^2)*app.s.grn_L); + if app.RegressionModel.Value == "Constant OF" + app.x.OF = app.s.const_OF; + elseif app.RegressionModel.Value == "Shifting OF" + app.x.OF = 0; + end + app.x.mdot_f = 0; + app.x.mdot_n = 0; + app.x.rdot = 0; + app.x.grn_ID = app.s.grn_ID; + + app.t = 0; + + app.o.t = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.m_o = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.P_tnk = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.P_cmbr = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.mdot_o = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.mdot_f = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.OF = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.grn_ID = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.mdot_n = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.rdot = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.m_f = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.F_thr = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.dP = zeros(1,app.s.tmax/app.s.dt + 1); + + app.o.m_o(1) = app.x.m_o; + app.o.P_tnk(1) = app.x.P_tnk; + app.o.P_cmbr(1) = app.x.P_cmbr; + app.o.mdot_o(1) = app.x.mdot_o; + app.o.mdot_f(1) = app.x.mdot_f; + app.o.OF(1) = app.x.OF; + app.o.grn_ID(1) = app.x.grn_ID; + app.o.mdot_n(1) = app.x.mdot_n; + app.o.rdot(1) = app.x.rdot; + app.o.m_f(1) = app.x.m_f; + + if app.s.mp_calc == 1 + app.o.m_t = zeros(1,app.s.tmax/app.s.dt + 1); + app.o.cg = zeros(1,app.s.tmax/app.s.dt + 1); + + mp = mass(app.s,app.x); + + app.o.m_t(1) = mp(1); + app.o.cg(1) = mp(2); + end + + % RUN HRAP SIMULATION + + [app.s,app.x,app.o,app.t] = sim_loop(app.s,app.x,app.o,app.t); + + % Sim Results + + totalImpulse = trapz(app.o.t(app.o.F_thr>0),app.o.F_thr(app.o.F_thr>0)); + + [motorClass,percent] = impulse(totalImpulse); + + %Display Motor Performance + + burnTime = app.o.t(sum(app.o.F_thr>0)); + peakF_thr = max(app.o.F_thr); + averageF_thr = mean(app.o.F_thr(app.o.F_thr>0)); + peakPressure = max(app.o.P_cmbr)/10^5; + averagePressure = mean(app.o.P_cmbr(app.o.P_cmbr>0))/10^5; + fuelConsumed = (app.o.m_f(1)-app.o.m_f(end)); + oxidizerConsumed = (app.o.m_o(1)-app.o.m_o(end)); + averageOF = oxidizerConsumed/fuelConsumed; + intPressure = trapz(app.o.t,app.o.P_cmbr); + Cstar = intPressure*0.25*pi*app.s.noz_thrt^2/(fuelConsumed+oxidizerConsumed); + specificImpulse = totalImpulse/(((oxidizerConsumed+fuelConsumed))*9.81); + fuelLeft = app.x.grn_ID*100; + + app.PerformanceSummary.Value = sprintf('Motor Name: %s\n Propellant: %s\n Oxidizer Tank Volume: %0.0f cc\n Burn Time: %0.3f s\n Peak Thrust: %0.1f N\n Average Thrust: %0.1f N\n Total Impulse: %0.2f N-s\n Peak Chamber Pressure: %0.3f bar\n Average Chamber Pressure: %0.3f bar\n Port Diameter at Burnout: %0.3f cm\n Fuel Consumed: %0.3f kg\n Oxidizer Consumed: %0.3f kg\n Average OF Ratio: %0.3f\n Characteristic Velocity: %0.1f m/s\n Specific Impulse: %0.1f s\n Motor Classification: %3.0f%% %s%0.0f', app.s.mtr_nm,app.s.prop_nm,app.s.tnk_V*10^6,burnTime,peakF_thr,averageF_thr,totalImpulse,peakPressure,averagePressure,fuelLeft,fuelConsumed,oxidizerConsumed,averageOF,Cstar,specificImpulse,percent,motorClass,averageF_thr); + + app.PlotTitle.Enable = 1; + app.PlotTitle.Editable = 1; + app.PlotTitleEditFieldLabel.Enable = 1; + app.Xaxis.Enable = 1; + app.Xaxis.Editable = 1; + app.XaxisDropDownLabel.Enable = 1; + app.Yaxis.Enable = 1; + app.Yaxis.Editable = 1; + app.YaxisDropDownLabel.Enable = 1; + app.XaxisUnits.Enable = 1; + app.XaxisUnits.Editable = 1; + app.XaxisUnitsDropDownLabel.Enable = 1; + app.YaxisUnits.Enable = 1; + app.YaxisUnits.Editable = 1; + app.YaxisUnitsDropDownLabel.Enable = 1; + app.Legend.Enable = 1; + app.Legend.Editable = 1; + app.LegendEditFieldLabel.Enable = 1; + app.AddPlot.Enable = 1; + app.ClearPlot.Enable = 1; + app.SavePlot.Enable = 1; + app.SaveResults.Enable = 1; + if app.MassProperties.Value == 1 + app.ExportRSE.Enable = 1; + end + app.ExportCSV.Enable = 1; + + app.RunSimulation.BackgroundColor = [0.96,0.96,0.96]; + + end + + % Value changed function: Xaxis + function x_axis(app, event) + value = app.Xaxis.Value; + + if value == "Time" + app.XaxisUnits.Items = {'s'}; + elseif value == "Oxidizer Mass" + app.XaxisUnits.Items = {'lbm','kg','g','oz'}; + elseif value == "Fuel Mass" + app.XaxisUnits.Items = {'lbm','kg','g','oz'}; + elseif value == "Total Motor Mass" + app.XaxisUnits.Items = {'lbm','kg','g','oz'}; + elseif value == "Center of Mass" + app.XaxisUnits.Items = {'in','ft','mm','cm','m'}; + elseif value == "Chamber Pressure" + app.XaxisUnits.Items = {'psi','psf','atm','Pa','kPa','Bar','MPa'}; + elseif value == "Total Propellant Mass" + app.XaxisUnits.Items = {'lbm','kg','g','oz'}; + elseif value == "Tank Pressure" + app.XaxisUnits.Items = {'psi','psf','atm','Pa','kPa','Bar','MPa'}; + elseif value == "Oxidizer Mass Flow Rate" + app.XaxisUnits.Items = {'lbm/s','kg/s','g/s','oz/s'}; + elseif value == "Fuel Mass Flow Rate" + app.XaxisUnits.Items = {'lbm/s','kg/s','g/s','oz/s'}; + elseif value == "Total Mass Flow Rate" + app.XaxisUnits.Items = {'lbm/s','kg/s','g/s','oz/s'}; + elseif value == "OF Ratio" + app.XaxisUnits.Items = {''}; + elseif value == "Regression Rate" + app.XaxisUnits.Items = {'in/s','ft/s','mm/s','cm/s','m/s'}; + elseif value == "Thrust" + app.XaxisUnits.Items = {'N','lbf','kgf'}; + elseif value == "Injector Pressure Drop" + app.XaxisUnits.Items = {'psi','psf','atm','Pa','kPa','Bar','MPa'}; + end + end + + % Value changed function: Yaxis + function y_axis(app, event) + value = app.Yaxis.Value; + + if value == "Time" + app.YaxisUnits.Items = {'s'}; + app.Legend.Value = 'Time'; + elseif value == "Oxidizer Mass" + app.YaxisUnits.Items = {'lbm','kg','g','oz'}; + app.Legend.Value = 'Oxidizer Mass'; + elseif value == "Fuel Mass" + app.YaxisUnits.Items = {'lbm','kg','g','oz'}; + app.Legend.Value = 'Fuel Mass'; + elseif value == "Total Motor Mass" + app.YaxisUnits.Items = {'lbm','kg','g','oz'}; + app.Legend.Value = 'Total Motor Mass'; + elseif value == "Center of Mass" + app.YaxisUnits.Items = {'in','ft','mm','cm','m'}; + app.Legend.Value = 'Center of Mass'; + elseif value == "Chamber Pressure" + app.YaxisUnits.Items = {'psi','psf','atm','Pa','kPa','Bar','MPa'}; + app.Legend.Value = 'Chamber Pressure'; + elseif value == "Total Propellant Mass" + app.YaxisUnits.Items = {'lbm','kg','g','oz'}; + app.Legend.Value = 'Total Propellant Mass'; + elseif value == "Tank Pressure" + app.YaxisUnits.Items = {'psi','psf','atm','Pa','kPa','Bar','MPa'}; + app.Legend.Value = 'Tank Pressure'; + elseif value == "Oxidizer Mass Flow Rate" + app.YaxisUnits.Items = {'lbm/s','kg/s','g/s','oz/s'}; + app.Legend.Value = 'Oxidizer Mass Flow Rate'; + elseif value == "Fuel Mass Flow Rate" + app.YaxisUnits.Items = {'lbm/s','kg/s','g/s','oz/s'}; + app.Legend.Value = 'Fuel Mass Flow Rate'; + elseif value == "Total Mass Flow Rate" + app.YaxisUnits.Items = {'lbm/s','kg/s','g/s','oz/s'}; + app.Legend.Value = 'Total Mass Flow Rate'; + elseif value == "OF Ratio" + app.YaxisUnits.Items = {''}; + app.Legend.Value = 'OF Ratio'; + elseif value == "Regression Rate" + app.YaxisUnits.Items = {'in/s','ft/s','mm/s','cm/s','m/s'}; + app.Legend.Value = 'Regression Rate'; + elseif value == "Thrust" + app.YaxisUnits.Items = {'N','lbf','kgf'}; + app.Legend.Value = 'Thrust'; + elseif value == "Injector Pressure Drop" + app.YaxisUnits.Items = {'psi','psf','atm','Pa','kPa','Bar','MPa'}; + app.Legend.Value = 'Injector Pressure Drop'; + end + end + + % Button pushed function: AddPlot + function plot(app, event) + value = app.Xaxis.Value; + + if value == "Time" + app.plt.x = app.o.t; + elseif value == "Oxidizer Mass" + app.plt.x = app.o.m_o; + elseif value == "Fuel Mass" + app.plt.x = app.o.m_f; + elseif value == "Total Motor Mass" + app.plt.x = app.o.m_f+app.o.m_o+app.o.m_t; + elseif value == "Center of Mass" + app.plt.x = app.o.cg; + elseif value == "Chamber Pressure" + app.plt.x = app.o.P_cmbr; + elseif value == "Total Propellant Mass" + app.plt.x = app.o.m_f+app.o.m_o; + elseif value == "Tank Pressure" + app.plt.x = app.o.P_tnk; + elseif value == "Oxidizer Mass Flow Rate" + app.plt.x = app.o.mdot_o; + elseif value == "Fuel Mass Flow Rate" + app.plt.x = app.o.mdot_f; + elseif value == "Total Mass Flow Rate" + app.plt.x = app.o.mdot_n; + elseif value == "OF Ratio" + app.plt.x = app.o.OF; + elseif value == "Regression Rate" + app.plt.x = app.o.rdot; + elseif value == "Thrust" + app.plt.x = app.o.F_thr; + elseif value == "Injector Pressure Drop" + app.plt.x = app.o.P_tnk - app.o.P_cmbr; + end + + value = app.Yaxis.Value; + + if value == "Time" + app.plt.y = app.o.t; + elseif value == "Oxidizer Mass" + app.plt.y = app.o.m_o; + elseif value == "Fuel Mass" + app.plt.y = app.o.m_f; + elseif value == "Total Motor Mass" + app.plt.y = app.o.m_f+app.o.m_o+app.o.m_t; + elseif value == "Center of Mass" + app.plt.y = app.o.cg; + elseif value == "Chamber Pressure" + app.plt.y = app.o.P_cmbr; + elseif value == "Total Propellant Mass" + app.plt.y = app.o.m_f+app.o.m_o; + elseif value == "Tank Pressure" + app.plt.y = app.o.P_tnk; + elseif value == "Oxidizer Mass Flow Rate" + app.plt.y = app.o.mdot_o; + elseif value == "Fuel Mass Flow Rate" + app.plt.y = app.o.mdot_f; + elseif value == "Total Mass Flow Rate" + app.plt.y = app.o.mdot_n; + elseif value == "OF Ratio" + app.plt.y = app.o.OF; + elseif value == "Regression Rate" + app.plt.y = app.o.rdot; + elseif value == "Thrust" + app.plt.y = app.o.F_thr; + elseif value == "Injector Pressure Drop" + app.plt.y = app.o.P_tnk - app.o.P_cmbr; + end + + value = app.XaxisUnits.Value; + + if value == "lbm" + elseif value == "kg" + app.u.x_axis = 1; + elseif value == "g" + app.u.x_axis = 1/0.001; + elseif value == "oz" + app.u.x_axis = 1/0.0283495; + elseif value == "in" + app.u.x_axis = 1/0.0254; + elseif value == "ft" + app.u.x_axis = 1/0.3048; + elseif value == "mm" + app.u.x_axis = 1/0.001; + elseif value == "cm" + app.u.x_axis = 1/0.01'; + elseif value == "m" + app.u.x_axis = 1; + elseif value == "psi" + app.u.x_axis = 1/6894.76; + elseif value == "psf" + app.u.x_axis = 1/147.8802777777778; + elseif value == "atm" + app.u.x_axis = 1/101325; + elseif value == "Pa" + app.u.x_axis = 1; + elseif value == "kPa" + app.u.x_axis = 1/1000; + elseif value == "Bar" + app.u.x_axis = 1/100000; + elseif value == "MPa" + app.u.x_axis = 1/1000000; + elseif value == "lbm/s" + app.u.x_axis = 1/0.453592; + elseif value == "kg/s" + app.u.x_axis = 1; + elseif value == "g/s" + app.u.x_axis = 1/0.001; + elseif value == "oz/s" + app.u.x_axis = 1/0.0283495; + elseif value == "in/s" + app.u.x_axis = 1/0.0254; + elseif value == "ft/s" + app.u.x_axis = 1/0.3048; + elseif value == "mm/s" + app.u.x_axis = 1/0.001; + elseif value == "cm/s" + app.u.x_axis = 1/0.01; + elseif value == "m/s" + app.u.x_axis = 1; + elseif value == "N" + app.u.x_axis = 1; + elseif value == "lbf" + app.u.x_axis = 1/4.44822; + elseif value == "kgf" + app.u.x_axis = 1/9.80665; + else + app.u.x_axis = 1; + end + + value = app.YaxisUnits.Value; + + if value == "lbm" + elseif value == "kg" + app.u.y_axis = 1; + elseif value == "g" + app.u.y_axis = 1000; + elseif value == "oz" + app.u.y_axis = 35.28; + elseif value == "in" + app.u.y_axis = 1/0.0254; + elseif value == "ft" + app.u.y_axis = 1/0.3048; + elseif value == "mm" + app.u.y_axis = 1000; + elseif value == "cm" + app.u.y_axis = 100; + elseif value == "m" + app.u.y_axis = 1; + elseif value == "psi" + app.u.y_axis = 1/6894.76; + elseif value == "psf" + app.u.y_axis = 1/147.8802777777778; + elseif value == "atm" + app.u.y_axis = 1/101325; + elseif value == "Pa" + app.u.y_axis = 1; + elseif value == "kPa" + app.u.y_axis = 0.001; + elseif value == "Bar" + app.u.y_axis = 0.00001; + elseif value == "MPa" + app.u.y_axis = 0.000001; + elseif value == "lbm/s" + app.u.y_axis = 1/0.453592; + elseif value == "kg/s" + app.u.y_axis = 1; + elseif value == "g/s" + app.u.y_axis = 1/0.001; + elseif value == "oz/s" + app.u.y_axis = 1/0.0283495; + elseif value == "in/s" + app.u.y_axis = 1/0.0254; + elseif value == "ft/s" + app.u.y_axis = 1/0.3048; + elseif value == "mm/s" + app.u.y_axis = 1/0.001; + elseif value == "cm/s" + app.u.y_axis = 1/0.01; + elseif value == "m/s" + app.u.y_axis = 1; + elseif value == "N" + app.u.y_axis = 1; + elseif value == "lbf" + app.u.y_axis = 1/4.44822; + elseif value == "kgf" + app.u.y_axis = 1/9.80665; + else + app.u.y_axis = 1; + end + + plot(app.UIAxes,(app.plt.x.*app.u.x_axis),(app.plt.y.*app.u.y_axis),'DisplayName',app.Legend.Value) %#ok + app.UIAxes.Title.String = app.PlotTitle.Value; + + xvalue = app.Xaxis.Value; + if xvalue == "Time" + app.plt.xlabel = append('Time (',app.XaxisUnits.Value,')'); + elseif xvalue == "Oxidizer Mass" + app.plt.xlabel = append('Mass (',app.XaxisUnits.Value,')'); + elseif xvalue == "Fuel Mass" + app.plt.xlabel = append('Mass (',app.XaxisUnits.Value,')'); + elseif xvalue == "Total Mass" + app.plt.xlabel = append('Mass (',app.XaxisUnits.Value,')'); + elseif xvalue == "Center of Mass" + app.plt.xlabel = append('Center of Mass (',app.XaxisUnits.Value,')'); + elseif xvalue == "Chamber Pressure" + app.plt.xlabel = append('Pressure (',app.XaxisUnits.Value,')'); + elseif xvalue == "Total Propellant Mass" + app.plt.xlabel = append('Mass (',app.XaxisUnits.Value,')'); + elseif xvalue == "Tank Pressure" + app.plt.xlabel = append('Pressure (',app.XaxisUnits.Value,')'); + elseif xvalue == "Oxidizer Mass Flow Rate" + app.plt.xlabel = append('Mass Flow Rate (',app.XaxisUnits.Value,')'); + elseif xvalue == "Fuel Mass Flow Rate" + app.plt.xlabel = append('Mass Flow Rate (',app.XaxisUnits.Value,')'); + elseif xvalue == "Total Mass Flow Rate" + app.plt.xlabel = append('Mass Flow Rate (',app.XaxisUnits.Value,')'); + elseif xvalue == "OF Ratio" + app.plt.xlabel = 'OF Ratio'; + elseif xvalue == "Regression Rate" + app.plt.xlabel = append('Regression Rate (',app.XaxisUnits.Value,')'); + elseif xvalue == "Thrust" + app.plt.xlabel = append('Thrust (',app.XaxisUnits.Value,')'); + elseif xvalue == "Injector Pressure Drop" + app.plt.xlabel = append('Pressure (',app.XaxisUnits.Value,')'); + end + + yvalue = app.Yaxis.Value; + if yvalue == "Time" + app.plt.ylabel = append('Time (',app.YaxisUnits.Value,')'); + elseif yvalue == "Oxidizer Mass" + app.plt.ylabel = append('Mass (',app.YaxisUnits.Value,')'); + elseif yvalue == "Fuel Mass" + app.plt.ylabel = append('Mass (',app.YaxisUnits.Value,')'); + elseif yvalue == "Total Mass" + app.plt.ylabel = append('Mass (',app.YaxisUnits.Value,')'); + elseif yvalue == "Center of Mass" + app.plt.ylabel = append('Center of Mass (',app.YaxisUnits.Value,')'); + elseif yvalue == "Chamber Pressure" + app.plt.ylabel = append('Pressure (',app.YaxisUnits.Value,')'); + elseif yvalue == "Total Propellant Mass" + app.plt.ylabel = append('Mass (',app.YaxisUnits.Value,')'); + elseif yvalue == "Tank Pressure" + app.plt.ylabel = append('Pressure (',app.YaxisUnits.Value,')'); + elseif yvalue == "Oxidizer Mass Flow Rate" + app.plt.ylabel = append('Mass Flow Rate (',app.YaxisUnits.Value,')'); + elseif yvalue == "Fuel Mass Flow Rate" + app.plt.ylabel = append('Mass Flow Rate (',app.YaxisUnits.Value,')'); + elseif yvalue == "Total Mass Flow Rate" + app.plt.ylabel = append('Mass Flow Rate (',app.YaxisUnits.Value,')'); + elseif yvalue == "OF Ratio" + app.plt.ylabel = 'OF Ratio'; + elseif yvalue == "Regression Rate" + app.plt.ylabel = append('Regression Rate (',app.YaxisUnits.Value,')'); + elseif yvalue == "Thrust" + app.plt.ylabel = append('Thrust (',app.YaxisUnits.Value,')'); + elseif yvalue == "Injector Pressure Drop" + app.plt.ylabel = append('Pressure (',app.YaxisUnits.Value,')'); + end + + app.UIAxes.XLabel.String = app.plt.xlabel; + app.UIAxes.YLabel.String = app.plt.ylabel; + hold(app.UIAxes,"on") + legend(app.UIAxes) + end + + % Button pushed function: ClearPlot + function clear_plot(app, event) + reset(app.UIAxes); + cla(app.UIAxes); + end + + % Button pushed function: SavePlot + function save_plot(app, event) + [file,path] = uiputfile('*.png','Save Plot',sprintf('%s\\output\\%s.png',app.appDir,app.PlotTitle.Value)); + + if isequal(file,0) + else + filepath = convertCharsToStrings(fullfile(path,file)); + exportgraphics(app.UIAxes,filepath); + end + end + + % Button pushed function: SaveResults + function save_results(app, event) + totalImpulse = trapz(app.o.t(app.o.F_thr>0),app.o.F_thr(app.o.F_thr>0)); + [motorClass,percent] = impulse(totalImpulse); + burnTime = app.o.t(sum(app.o.F_thr>0)); + peakF_thr = max(app.o.F_thr); + averageF_thr = mean(app.o.F_thr(app.o.F_thr>0)); + peakPressure = max(app.o.P_cmbr)/10^5; + averagePressure = mean(app.o.P_cmbr(app.o.P_cmbr>0))/10^5; + fuelConsumed = (app.o.m_f(1)-app.o.m_f(end)); + oxidizerConsumed = (app.o.m_o(1)-app.o.m_o(end)); + averageOF = oxidizerConsumed/fuelConsumed; + intPressure = trapz(app.o.t,app.o.P_cmbr); + Cstar = intPressure*0.25*pi*app.s.noz_thrt^2/(fuelConsumed+oxidizerConsumed); + specificImpulse = totalImpulse/(((oxidizerConsumed+fuelConsumed))*9.81); + fuelLeft = app.x.grn_ID*100; + + [file,path] = uiputfile('*.txt','Save Results Summary',sprintf('%s\\output\\sim_results.txt',app.appDir)); + + if isequal(file,0) + else + fileID = fopen(convertCharsToStrings(fullfile(path,file)),'w'); + fprintf(fileID,'Motor Name: %s\n Propellant: %s\n Oxidizer Tank Volume: %0.0f cc\n Burn Time: %0.3f s\n Peak Thrust: %0.1f N\n Average Thrust: %0.1f N\n Total Impulse: %0.2f N-s\n Peak Chamber Pressure: %0.3f bar\n Average Chamber Pressure: %0.3f bar\n Port Diameter at Burnout: %0.3f cm\n Fuel Consumed: %0.3f kg\n Oxidizer Consumed: %0.3f kg\n Average OF Ratio: %0.3f\n Characteristic Velocity: %0.1f m/s\n Specific Impulse: %0.1f s\n Motor Classification: %3.0f%% %s%0.0f', app.s.mtr_nm,app.s.prop_nm,app.s.tnk_V*10^6,burnTime,peakF_thr,averageF_thr,totalImpulse,peakPressure,averagePressure,fuelLeft,fuelConsumed,oxidizerConsumed,averageOF,Cstar,specificImpulse,percent,motorClass,averageF_thr); + fclose('all'); + end + end + + % Button pushed function: ExportRSE + function save_rse(app, event) + if app.MassProperties.Value == 1 + %Input template + cg = app.o.cg*1000; %array of cg (millimeters from forward end of motor) as function of time + f = app.o.F_thr; %array of force (newtons) as function of time + m = app.o.m_t*1000; %array of mass (grams) as function of time + t = app.o.t; %array of time (seconds) for other variables + Itot = trapz(app.o.t(app.o.F_thr>0),app.o.F_thr(app.o.F_thr>0)); %total impulse, newton-seconds + propWt = ((app.o.m_f(1)-app.o.m_f(end)) + (app.o.m_o(1)-app.o.m_o(end)))*1000; %propellant weight, grams + Isp = Itot/((propWt*1000)*9.81); %specific impulse, seconds + Type = 'Hybrid'; + avgThrust = mean(app.o.F_thr); %average thrust, newtons + burn_time = max(app.o.t); %burn time, seconds + motorClass = impulse(Itot); + prompt = {'Enter Motor Diameter in mm:','Enter Motor Length in mm:','Enter Motor Manufacturer:','Input Motor Notes:'}; + dlgtitle = 'RSE Export'; + dims = [1 12; 1 12; 1 50; 5 50]; + definput = {'0','0','Research',''}; + answer = inputdlg(prompt,dlgtitle,dims,definput); + code = sprintf('%s%0.0f',motorClass,avgThrust); + dia = str2double(answer{1}); %motor diameter, millimeters + exitDia = (app.s.noz_thrt*app.s.noz_ER^0.5)*1000; %nozzle exit diameter, millimeters + initWt = app.o.m_t(1)*1000; %loaded motor weight before ignition, grams + len = str2double(answer{2}); %motor length, millimeters + massFrac = (app.o.m_t(1)-app.o.m_t(length(app.o.m_t)))/app.o.m_t(1); %mass fraction, ratio of propellant mass to total mass + mfg = answer{3}; + peakThrust = max(app.o.F_thr); %peak thrust, newtons + throatDia = app.s.noz_thrt*1000; + comments = answer{4}; + + [file,path] = uiputfile('*.rse','Save RSE File',sprintf('%s\\output\\%s.rse',app.appDir,code)); + + if isequal(file,0) + else + fileID = fopen(convertCharsToStrings(fullfile(path,file)),'w'); + fprintf(fileID,'\n \n'); + fprintf(fileID,' ',Isp, Itot, Type, avgThrust, burn_time, code, dia, exitDia, initWt, len, massFrac, mfg, peakThrust, propWt, throatDia); + fprintf(fileID,'\n \n'); + fprintf(fileID,' %s\n',comments); + fprintf(fileID,' \n'); + fprintf(fileID,' \n'); + + for i = 1:length(cg) + fprintf(fileID,' \n',cg(i),f(i),m(i),t(i)); + end + + fprintf(fileID,' \n'); + fprintf(fileID,' \n'); + fprintf(fileID,' \n'); + fprintf(fileID,'\n'); + fclose('all'); + end + end + end + + % Button pushed function: ExportCSV + function save_csv(app, event) + [file,path] = uiputfile('*.csv','Save CSV Output',sprintf('%s\\output\\HRAP_output.csv',app.appDir)); + + if isequal(file,0) + else + filename = convertCharsToStrings(fullfile(path,file)); + if app.MassProperties.Value == 1 + M = ["Time (s)","Thrust (N)","Oxidizer Tank Pressure (kPa)","Combustion Chamber Pressure (kPa)","Injector Pressure Drop (kPa)","Oxidizer Mass (kg)","Fuel Mass (kg)","Total Motor Mass (kg)","Oxidizer Mass Flow Rate (kg/s)","Fuel Mass Flow Rate (kg/s)","Exhaust Mass Flow Rate (kg/s)","Oxidizer to Fuel Ratio","Grain ID (cm)","Regression Rate (mm/s)","Motor Center of Mass (cm)"; + app.o.t', app.o.F_thr', app.o.P_tnk'./1000, app.o.P_cmbr'./1000, app.o.dP'./1000, app.o.m_o', app.o.m_f', app.o.m_t', app.o.mdot_o', app.o.mdot_f', app.o.mdot_n', app.o.OF', app.o.grn_ID'.*100, app.o.rdot', app.o.cg'.*100]; + else + M = ["Time (s)","Thrust (N)","Oxidizer Tank Pressure (kPa)","Combustion Chamber Pressure (kPa)","Injector Pressure Drop (kPa)","Oxidizer Mass (kg)","Fuel Mass (kg)","Oxidizer Mass Flow Rate (kg/s)","Fuel Mass Flow Rate (kg/s)","Exhaust Mass Flow Rate (kg/s)","Oxidizer to Fuel Ratio","Grain ID (cm)","Regression Rate (mm/s)"; + app.o.t', app.o.F_thr', app.o.P_tnk'./1000, app.o.P_cmbr'./1000, app.o.dP'./1000, app.o.m_o', app.o.m_f', app.o.mdot_o', app.o.mdot_f', app.o.mdot_n', app.o.OF', app.o.grn_ID'.*100, app.o.rdot']; + end + + writematrix(M,filename); + fclose('all'); + end + + end + + % Button pushed function: SaveMotor + function save_config(app, event) + + cfg.mtr_nm = app.MotorConfiguration.Value; + + % Tank Dimensions Tab + cfg.tnk_V = app.TankVolume.Value; + cfg.tnk_V_unit = app.tnk_V_unit.Value; + cfg.tnk_V_state = app.TankVolumeByDimensions.Value; + cfg.tnk_L = app.TankLength.Value; + cfg.tnk_L_unit = app.tnk_L_unit.Value; + cfg.tnk_D = app.TankDiameter.Value; + cfg.tnk_D_unit = app.tnk_D_unit.Value; + cfg.cmbr_V_state = app.ChamberVolumeByDimensions.Value; + cfg.cmbr_V = app.ChamberVolume.Value; + cfg.cmbr_V_unit = app.cmbr_V_unit.Value; + + % Nozzle Configuration Tab + cfg.noz_thrt = app.ThroatDiameter.Value; + cfg.noz_thrt_unit = app.noz_thrt_unit.Value; + cfg.noz_def = app.NozzleDropDown.Value; + cfg.noz_ex = app.NozzleExit.Value; + cfg.noz_ex_unit = app.noz_ext_unit.Value; + cfg.noz_eff = app.NozzleEfficiency.Value; + cfg.noz_Cd = app.NozzleCd.Value; + + % Mass Properties + cfg.mp_state = app.MassProperties.Value; + cfg.tnk_X = app.TankLocation.Value; + cfg.tnk_X_unit = app.tnk_X_unit.Value; + cfg.cmbr_X = app.GrainLocation.Value; + cfg.cmbr_X_unit = app.cmbr_X_unit.Value; + cfg.mtr_cg = app.MotorCG.Value; + cfg.mtr_cg_unit = app.mtr_cg_unit.Value; + cfg.mtr_m = app.MotorMass.Value; + cfg.mtr_m_unit = app.mtr_m_unit.Value; + + % Initial Conditions + cfg.tnk_dd = app.TankDropDown.Value; + cfg.tnk_cond = app.TankCond.Value; + if app.TankDropDown.Value == "Starting Tank Temperature" + cfg.T_tnk_unit = app.T_tnk_unit.Value; + elseif app.TankDropDown.Value == "Starting Tank Pressure" + cfg.T_tnk_unit = app.T_tnk_unit.Value; + end + cfg.P_cmbr = app.ChamberPressure.Value; + cfg.P_cmbr_unit = app.P_cmbr_unit.Value; + cfg.fill_dd = app.OxidizerDropDown.Value; + cfg.fill = app.OxidizerFill.Value; + cfg.fill_unit = app.m_O_unit.Value; + cfg.Pa = app.AmbientPressure.Value; + cfg.Pa_unit = app.Pa_unit.Value; + + % Propellant Configuration + cfg.prop_file = app.s.prop_file; + cfg.prop_nm = app.PropellantName.Value; + cfg.prop_rho = app.PropellantDensity.Value; + cfg.prop_rho_unit = app.density_unit.Value; + cfg.prop_a = app.RegressionCoefficient.Value; + cfg.prop_n = app.RegressionExponent.Value; + cfg.prop_m = app.LengthExponent.Value; + cfg.const_OF = app.ConstantOF.Value; + cfg.cstar_eff = app.CEfficiency.Value; + cfg.grn_ID = app.GrainID.Value; + cfg.grn_ID_unit = app.grnID_unit.Value; + cfg.grn_OD = app.GrainOD.Value; + cfg.grn_OD_unit = app.grnOD_unit.Value; + cfg.grn_L = app.GrainLength.Value; + cfg.grn_L_unit = app.grnL_unit.Value; + + % Injector Configuration + cfg.inj_D = app.InjectorDiameter.Value; + cfg.inj_D_unit = app.inj_D_unit.Value; + cfg.inj_N = app.NumberofInjectors.Value; + cfg.inj_Cd = app.InjectorCd.Value; + cfg.vnt_state = app.VentState.Value; + cfg.vnt_D = app.VentDiameter.Value; + cfg.vnt_D_unit = app.vnt_D_unit.Value; + cfg.vnt_Cd = app.VentCd.Value; + + % Simulation Configuration + cfg.t_max = app.RunTime.Value; + cfg.t_burn = app.BurnTime.Value; + cfg.dt = app.Timestep.Value; + cfg.reg_model = app.RegressionModel.Value; + + [file,path] = uiputfile('*.mat','Save Motor Configuration',sprintf('%s\\motor_configs\\%s.mat',app.appDir,app.MotorConfiguration.Value)); + + if isequal(file,0) + else + filename = convertCharsToStrings(fullfile(path,file)); + save(filename,'cfg'); + fclose('all'); + end + end + + % Button pushed function: + % ClickhereformoreinformationregardingHRAPitsusageandButton + function open_hrap_document(app, event) + winopen('Theory and Application of the Hybrid Rocket Analysis Program (HRAP).pdf'); + end + + % Button pushed function: LoadMotor + function load_config(app, event) + [file,path,~] = uigetfile('*.mat','Select a configuration file',sprintf('%s\\motor_configs',app.appDir)); + + if isequal(file,0) + else + load(fullfile(path, file)); %#ok + + app.MotorConfiguration.Value = cfg.mtr_nm; %#ok<*NODEF> + + % Tank Dimensions Tab + app.TankVolumeByDimensions.Value = cfg.tnk_V_state; + app.TankLength.Value = cfg.tnk_L; + app.tnk_L_unit.Value = cfg.tnk_L_unit; + app.TankDiameter.Value = cfg.tnk_D; + app.tnk_D_unit.Value = cfg.tnk_D_unit; + app.TankVolumeByDimensions.Value = cfg.tnk_V_state; + app.TankVolume.Value = cfg.tnk_V; + app.tnk_V_unit.Value = cfg.tnk_V_unit; + app.ChamberVolumeByDimensions.Value = cfg.cmbr_V_state; + app.ChamberVolume.Value = cfg.cmbr_V; + app.cmbr_V_unit.Value = cfg.cmbr_V_unit; + + % Nozzle Configuration Tab + app.ThroatDiameter.Value = cfg.noz_thrt; + app.noz_thrt_unit.Value = cfg.noz_thrt_unit; + app.NozzleDropDown.Value = cfg.noz_def; + app.NozzleExit.Value = cfg.noz_ex; + app.noz_ext_unit.Value = cfg.noz_ex_unit; + app.NozzleEfficiency.Value = cfg.noz_eff; + app.NozzleCd.Value = cfg.noz_Cd; + + % Mass Properties + app.MassProperties.Value = cfg.mp_state; + app.TankLocation.Value = cfg.tnk_X; + app.tnk_X_unit.Value = cfg.tnk_X_unit; + app.GrainLocation.Value = cfg.cmbr_X; + app.cmbr_X_unit.Value = cfg.cmbr_X_unit; + app.MotorCG.Value = cfg.mtr_cg; + app.mtr_cg_unit.Value = cfg.mtr_cg_unit; + app.MotorMass.Value = cfg.mtr_m; + app.mtr_m_unit.Value = cfg.mtr_m_unit; + + % Initial Conditions + app.TankDropDown.Value = cfg.tnk_dd; + app.TankCond.Value = cfg.tnk_cond; + if cfg.tnk_dd == "Starting Tank Temperature" + app.T_tnk_unit.Items = {'F','C','K','R'}; + app.T_tnk_unit.Value = cfg.T_tnk_unit; + else + app.T_tnk_unit.Items = {'psi','psf','atm','Pa','kPa','Bar','MPa'}; + app.T_tnk_unit.Value = cfg.T_tnk_unit; + end + app.ChamberPressure.Value = cfg.P_cmbr; + app.P_cmbr_unit.Value = cfg.P_cmbr_unit; + app.OxidizerDropDown.Value = cfg.fill_dd; + app.OxidizerFill.Value = cfg.fill; + app.m_O_unit.Value = cfg.fill_unit; + if app.OxidizerDropDown.Value == "Starting Oxidizer Mass" + app.m_O_unit.Items = {'lbm','kg','g','oz'}; + else + app.m_O_unit.Items = {'%'}; + end + app.AmbientPressure.Value = cfg.Pa; + app.Pa_unit.Value = cfg.Pa_unit; + + % Propellant Configuration + load(cfg.prop_file); + app.s.prop_file = cfg.prop_file; + app.s.prop_nm = s.prop_nm; %#ok<*ADPROPLC> + app.s.prop_k = s.prop_k; + app.s.prop_M = s.prop_M; + app.s.prop_OF = s.prop_OF; + app.s.prop_Pc = s.prop_Pc; + app.s.prop_Reg = s.prop_Reg; + app.s.prop_Rho = s.prop_Rho; + app.s.prop_T = s.prop_T; + app.s.opt_OF = s.opt_OF; + + app.PropellantName.Value = cfg.prop_nm; + app.PropellantDensity.Value = cfg.prop_rho; + app.density_unit.Value = cfg.prop_rho_unit; + app.RegressionCoefficient.Value = cfg.prop_a; + app.RegressionExponent.Value = cfg.prop_n; + app.LengthExponent.Value = cfg.prop_m; + app.ConstantOF.Value = cfg.const_OF; + app.CEfficiency.Value = cfg.cstar_eff; + app.GrainID.Value = cfg.grn_ID; + app.grnID_unit.Value = cfg.grn_ID_unit; + app.GrainOD.Value = cfg.grn_OD; + app.grnOD_unit.Value = cfg.grn_OD_unit; + app.GrainLength.Value = cfg.grn_L; + app.grnL_unit.Value = cfg.grn_L_unit; + + % Injector Configuration + app.InjectorDiameter.Value = cfg.inj_D; + app.inj_D_unit.Value = cfg.inj_D_unit; + app.NumberofInjectors.Value = cfg.inj_N; + app.InjectorCd.Value = cfg.inj_Cd; + app.VentState.Value = cfg.vnt_state; + app.VentDiameter.Value = cfg.vnt_D; + app.vnt_D_unit.Value = cfg.vnt_D_unit; + app.VentCd.Value = cfg.vnt_Cd; + + % Simulation Configuration + app.RunTime.Value = cfg.t_max; + app.BurnTime.Value = cfg.t_burn; + app.Timestep.Value = cfg.dt; + app.RegressionModel.Value = cfg.reg_model; + end + + value = app.MassProperties.Value; + + if value == 1 + app.TankLocation.Enable = 1; + app.TankLocation.Editable = 1; + app.OxidizerTankLocationEditFieldLabel.Enable = 1; + app.tnk_X_unit.Enable = 1; + app.tnk_X_unit.Editable = 1; + app.GrainLocation.Enable = 1; + app.GrainLocation.Editable = 1; + app.FuelGrainLocationEditFieldLabel.Enable = 1; + app.cmbr_X_unit.Enable = 1; + app.cmbr_X_unit.Editable = 1; + app.MotorCG.Enable = 1; + app.MotorCG.Editable = 1; + app.EmptyMotorCenterofMassEditFieldLabel.Enable = 1; + app.mtr_cg_unit.Enable = 1; + app.mtr_cg_unit.Editable = 1; + app.MotorMass.Enable = 1; + app.MotorMass.Editable = 1; + app.EmptyMotorMassEditFieldLabel.Enable = 1; + app.mtr_m_unit.Enable = 1; + app.mtr_m_unit.Editable = 1; + if app.TankVolumeByDimensions.Value == 0 + app.TankDiameter.Enable = 1; + app.TankDiameter.Editable = 1; + app.OxidizerTankDiameterEditFieldLabel.Enable = 1; + app.tnk_D_unit.Enable = 1; + app.tnk_D_unit.Editable = 1; + end + else + app.TankLocation.Enable = 0; + app.TankLocation.Editable = 0; + app.OxidizerTankLocationEditFieldLabel.Enable = 0; + app.tnk_X_unit.Enable = 0; + app.tnk_X_unit.Editable = 0; + app.GrainLocation.Enable = 0; + app.GrainLocation.Editable = 0; + app.FuelGrainLocationEditFieldLabel.Enable = 0; + app.cmbr_X_unit.Enable = 0; + app.cmbr_X_unit.Editable = 0; + app.MotorCG.Enable = 0; + app.MotorCG.Editable = 0; + app.EmptyMotorCenterofMassEditFieldLabel.Enable = 0; + app.mtr_cg_unit.Enable = 0; + app.mtr_cg_unit.Editable = 0; + app.MotorMass.Enable = 0; + app.MotorMass.Editable = 0; + app.EmptyMotorMassEditFieldLabel.Enable = 0; + app.mtr_m_unit.Enable = 0; + app.mtr_m_unit.Editable = 0; + if app.TankVolumeByDimensions.Value == 0 + app.TankDiameter.Enable = 0; + app.TankDiameter.Editable = 0; + app.OxidizerTankDiameterEditFieldLabel.Enable = 0; + app.tnk_D_unit.Enable = 0; + app.tnk_D_unit.Editable = 0; + end + end + + value = app.TankVolumeByDimensions.Value; + + if value == 1 + app.TankLength.Enable = 1; + app.TankLength.Editable = 1; + app.OxidizerTankLengthEditFieldLabel.Enable = 1; + app.tnk_L_unit.Enable = 1; + app.tnk_L_unit.Editable = 1; + if app.MassProperties.Value == 0 + app.TankDiameter.Enable = 1; + app.TankDiameter.Editable = 1; + app.OxidizerTankDiameterEditFieldLabel.Enable = 1; + app.tnk_D_unit.Enable = 1; + app.tnk_D_unit.Editable = 1; + end + app.TankVolume.Enable = 0; + app.TankVolume.Editable = 0; + app.OxidizerTankVolumeEditFieldLabel.Enable = 0; + app.tnk_V_unit.Enable = 0; + app.tnk_V_unit.Editable = 0; + else + app.TankLength.Enable = 0; + app.TankLength.Editable = 0; + app.OxidizerTankLengthEditFieldLabel.Enable = 0; + app.tnk_L_unit.Enable = 0; + app.tnk_L_unit.Editable = 0; + if app.MassProperties.Value == 0 + app.TankDiameter.Enable = 0; + app.TankDiameter.Editable = 0; + app.OxidizerTankDiameterEditFieldLabel.Enable = 0; + app.tnk_D_unit.Enable = 0; + app.tnk_D_unit.Editable = 0; + end + app.TankVolume.Enable = 1; + app.TankVolume.Editable = 1; + app.OxidizerTankVolumeEditFieldLabel.Enable = 1; + app.tnk_V_unit.Enable = 1; + app.tnk_V_unit.Editable = 1; + end + + value = app.ChamberVolumeByDimensions.Value; + + if value == 0 + app.ChamberVolume.Enable = 1; + app.ChamberVolume.Editable = 1; + app.CombustionChamberVolumeEditFieldLabel.Enable = 1; + app.cmbr_V_unit.Enable = 1; + app.cmbr_V_unit.Editable = 1; + else + app.ChamberVolume.Enable = 0; + app.ChamberVolume.Editable = 0; + app.CombustionChamberVolumeEditFieldLabel.Enable = 0; + app.cmbr_V_unit.Enable = 0; + app.cmbr_V_unit.Editable = 0; + end + + value = app.RegressionModel.Value; + + if value == "Shifting OF" + app.RegressionCoefficient.Enable = 1; + app.RegressionCoefficient.Editable = 1; + app.RegressionCoefficientaEditFieldLabel.Enable = 1; + app.a_unit.Enable = 1; + app.RegressionExponent.Enable = 1; + app.RegressionExponent.Editable = 1; + app.RegressionExponentnEditFieldLabel.Enable = 1; + app.n_unit.Enable = 1; + app.LengthExponent.Enable = 1; + app.LengthExponent.Editable = 1; + app.LengthExponentmEditFieldLabel.Enable = 1; + app.m_unit.Enable = 1; + app.ConstantOF.Enable = 0; + app.ConstantOF.Editable = 0; + app.ConstantOFRatioLabel.Enable = 0; + else + app.RegressionCoefficient.Enable = 0; + app.RegressionCoefficient.Editable = 0; + app.RegressionCoefficientaEditFieldLabel.Enable = 0; + app.a_unit.Enable = 0; + app.RegressionExponent.Enable = 0; + app.RegressionExponent.Editable = 0; + app.RegressionExponentnEditFieldLabel.Enable = 0; + app.n_unit.Enable = 0; + app.LengthExponent.Enable = 0; + app.LengthExponent.Editable = 0; + app.LengthExponentmEditFieldLabel.Enable = 0; + app.m_unit.Enable = 0; + app.ConstantOF.Enable = 1; + app.ConstantOF.Editable = 1; + app.ConstantOFRatioLabel.Enable = 1; + end + + value = app.NozzleDropDown.Value; + if value == "Nozzle Expansion Ratio" + app.noz_ext_unit.Enable = 0; + app.noz_ext_unit.Editable = 0; + app.noz_ext_unit.Visible = 0; + else + app.noz_ext_unit.Enable = 1; + app.noz_ext_unit.Editable = 1; + app.noz_ext_unit.Visible = 1; + end + + value = app.VentState.Value; + if value == "None" + app.VentCd.Enable = 0; + app.VentCd.Editable = 0; + app.VentDischargeCoefficientEditFieldLabel.Enable = 0; + app.VentDiameter.Enable = 0; + app.VentDiameter.Editable = 0; + app.VentDiameterEditFieldLabel.Enable = 0; + else + app.VentCd.Enable = 1; + app.VentCd.Editable = 1; + app.VentDischargeCoefficientEditFieldLabel.Enable = 1; + app.VentDiameter.Enable = 1; + app.VentDiameter.Editable = 1; + app.VentDiameterEditFieldLabel.Enable = 1; + end + end + end + + % Component initialization + methods (Access = private) + + % Create UIFigure and components + function createComponents(app) + + % Create HRAP_v2022_06_04 and hide until all components are created + app.HRAP_v2022_06_04 = uifigure('Visible', 'off'); + app.HRAP_v2022_06_04.Position = [100 100 850 889]; + app.HRAP_v2022_06_04.Name = 'MATLAB App'; + app.HRAP_v2022_06_04.Resize = 'off'; + + % Create AppTitle + app.AppTitle = uilabel(app.HRAP_v2022_06_04); + app.AppTitle.HorizontalAlignment = 'center'; + app.AppTitle.FontName = 'Times New Roman'; + app.AppTitle.FontSize = 24; + app.AppTitle.FontWeight = 'bold'; + app.AppTitle.Position = [250 850 353 40]; + app.AppTitle.Text = 'Hybrid Rocket Analysis Program'; + + % Create AppTabs + app.AppTabs = uitabgroup(app.HRAP_v2022_06_04); + app.AppTabs.Position = [11 20 830 830]; + + % Create MotorTab + app.MotorTab = uitab(app.AppTabs); + app.MotorTab.Title = 'Simulation Configuration'; + + % Create ConfigurationNameLabel + app.ConfigurationNameLabel = uilabel(app.MotorTab); + app.ConfigurationNameLabel.HorizontalAlignment = 'right'; + app.ConfigurationNameLabel.Position = [12 773 112 22]; + app.ConfigurationNameLabel.Text = 'Configuration Name'; + + % Create MotorConfiguration + app.MotorConfiguration = uieditfield(app.MotorTab, 'text'); + app.MotorConfiguration.Position = [143 773 268 22]; + app.MotorConfiguration.Value = 'mtr_cfg'; + + % Create TankDimensionsPanel + app.TankDimensionsPanel = uipanel(app.MotorTab); + app.TankDimensionsPanel.Title = 'Tank Dimensions'; + app.TankDimensionsPanel.Position = [11 566 400 200]; + + % Create TankVolumeByDimensions + app.TankVolumeByDimensions = uicheckbox(app.TankDimensionsPanel); + app.TankVolumeByDimensions.ValueChangedFcn = createCallbackFcn(app, @tank_dimensions, true); + app.TankVolumeByDimensions.Text = 'Calculate Oxidizer Tank Volume By Dimensions'; + app.TankVolumeByDimensions.Position = [11 122 278 22]; + + % Create OxidizerTankLengthEditFieldLabel + app.OxidizerTankLengthEditFieldLabel = uilabel(app.TankDimensionsPanel); + app.OxidizerTankLengthEditFieldLabel.HorizontalAlignment = 'right'; + app.OxidizerTankLengthEditFieldLabel.Enable = 'off'; + app.OxidizerTankLengthEditFieldLabel.Position = [1 92 118 22]; + app.OxidizerTankLengthEditFieldLabel.Text = 'Oxidizer Tank Length'; + + % Create TankLength + app.TankLength = uieditfield(app.TankDimensionsPanel, 'numeric'); + app.TankLength.Limits = [0 Inf]; + app.TankLength.Editable = 'off'; + app.TankLength.Enable = 'off'; + app.TankLength.Position = [230 92 100 22]; + + % Create OxidizerTankDiameterEditFieldLabel + app.OxidizerTankDiameterEditFieldLabel = uilabel(app.TankDimensionsPanel); + app.OxidizerTankDiameterEditFieldLabel.HorizontalAlignment = 'right'; + app.OxidizerTankDiameterEditFieldLabel.Enable = 'off'; + app.OxidizerTankDiameterEditFieldLabel.Position = [1 62 130 22]; + app.OxidizerTankDiameterEditFieldLabel.Text = 'Oxidizer Tank Diameter'; + + % Create TankDiameter + app.TankDiameter = uieditfield(app.TankDimensionsPanel, 'numeric'); + app.TankDiameter.Limits = [0 Inf]; + app.TankDiameter.Editable = 'off'; + app.TankDiameter.Enable = 'off'; + app.TankDiameter.Position = [230 62 100 22]; + + % Create OxidizerTankVolumeEditFieldLabel + app.OxidizerTankVolumeEditFieldLabel = uilabel(app.TankDimensionsPanel); + app.OxidizerTankVolumeEditFieldLabel.HorizontalAlignment = 'right'; + app.OxidizerTankVolumeEditFieldLabel.Position = [1 152 122 22]; + app.OxidizerTankVolumeEditFieldLabel.Text = 'Oxidizer Tank Volume'; + + % Create TankVolume + app.TankVolume = uieditfield(app.TankDimensionsPanel, 'numeric'); + app.TankVolume.Limits = [0 Inf]; + app.TankVolume.Position = [230 152 100 22]; + + % Create CombustionChamberVolumeEditFieldLabel + app.CombustionChamberVolumeEditFieldLabel = uilabel(app.TankDimensionsPanel); + app.CombustionChamberVolumeEditFieldLabel.HorizontalAlignment = 'right'; + app.CombustionChamberVolumeEditFieldLabel.Enable = 'off'; + app.CombustionChamberVolumeEditFieldLabel.Position = [1 32 166 22]; + app.CombustionChamberVolumeEditFieldLabel.Text = 'Combustion Chamber Volume'; + + % Create ChamberVolume + app.ChamberVolume = uieditfield(app.TankDimensionsPanel, 'numeric'); + app.ChamberVolume.Limits = [0 Inf]; + app.ChamberVolume.Editable = 'off'; + app.ChamberVolume.Enable = 'off'; + app.ChamberVolume.Position = [230 32 100 22]; + + % Create ChamberVolumeByDimensions + app.ChamberVolumeByDimensions = uicheckbox(app.TankDimensionsPanel); + app.ChamberVolumeByDimensions.ValueChangedFcn = createCallbackFcn(app, @combustion_chamber_volume, true); + app.ChamberVolumeByDimensions.Text = 'Calculate Combustion Chamber Volume By Grain Dimensions'; + app.ChamberVolumeByDimensions.Position = [11 6 356 22]; + app.ChamberVolumeByDimensions.Value = true; + + % Create tnk_L_unit + app.tnk_L_unit = uidropdown(app.TankDimensionsPanel); + app.tnk_L_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.tnk_L_unit.Enable = 'off'; + app.tnk_L_unit.Position = [329 92 62 22]; + app.tnk_L_unit.Value = 'cm'; + + % Create tnk_D_unit + app.tnk_D_unit = uidropdown(app.TankDimensionsPanel); + app.tnk_D_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.tnk_D_unit.Enable = 'off'; + app.tnk_D_unit.Position = [329 62 62 22]; + app.tnk_D_unit.Value = 'cm'; + + % Create cmbr_V_unit + app.cmbr_V_unit = uidropdown(app.TankDimensionsPanel); + app.cmbr_V_unit.Items = {'in^3', 'ft^3', 'cm^3', 'L', 'Gal', 'm^3'}; + app.cmbr_V_unit.Enable = 'off'; + app.cmbr_V_unit.Position = [329 32 62 22]; + app.cmbr_V_unit.Value = 'cm^3'; + + % Create tnk_V_unit + app.tnk_V_unit = uidropdown(app.TankDimensionsPanel); + app.tnk_V_unit.Items = {'in^3', 'ft^3', 'cm^3', 'L', 'Gal', 'm^3'}; + app.tnk_V_unit.Editable = 'on'; + app.tnk_V_unit.BackgroundColor = [1 1 1]; + app.tnk_V_unit.Position = [329 152 62 22]; + app.tnk_V_unit.Value = 'cm^3'; + + % Create NozzleConfigurationPanel + app.NozzleConfigurationPanel = uipanel(app.MotorTab); + app.NozzleConfigurationPanel.Title = 'Nozzle Configuration'; + app.NozzleConfigurationPanel.Position = [11 406 400 150]; + + % Create NozzleThroatDiameterEditFieldLabel + app.NozzleThroatDiameterEditFieldLabel = uilabel(app.NozzleConfigurationPanel); + app.NozzleThroatDiameterEditFieldLabel.HorizontalAlignment = 'right'; + app.NozzleThroatDiameterEditFieldLabel.Position = [1 99 132 22]; + app.NozzleThroatDiameterEditFieldLabel.Text = 'Nozzle Throat Diameter'; + + % Create ThroatDiameter + app.ThroatDiameter = uieditfield(app.NozzleConfigurationPanel, 'numeric'); + app.ThroatDiameter.Limits = [0 Inf]; + app.ThroatDiameter.Position = [230 99 100 22]; + + % Create NozzleExit + app.NozzleExit = uieditfield(app.NozzleConfigurationPanel, 'numeric'); + app.NozzleExit.Limits = [0 Inf]; + app.NozzleExit.Position = [230 69 100 22]; + app.NozzleExit.Value = 1; + + % Create NozzleEfficiencyEditFieldLabel + app.NozzleEfficiencyEditFieldLabel = uilabel(app.NozzleConfigurationPanel); + app.NozzleEfficiencyEditFieldLabel.HorizontalAlignment = 'right'; + app.NozzleEfficiencyEditFieldLabel.Position = [1 39 97 22]; + app.NozzleEfficiencyEditFieldLabel.Text = 'Nozzle Efficiency'; + + % Create NozzleEfficiency + app.NozzleEfficiency = uieditfield(app.NozzleConfigurationPanel, 'numeric'); + app.NozzleEfficiency.Limits = [0 100]; + app.NozzleEfficiency.ValueDisplayFormat = '%11.3g'; + app.NozzleEfficiency.Position = [230 39 100 22]; + app.NozzleEfficiency.Value = 100; + + % Create NozzleDischargeCoefficientEditFieldLabel + app.NozzleDischargeCoefficientEditFieldLabel = uilabel(app.NozzleConfigurationPanel); + app.NozzleDischargeCoefficientEditFieldLabel.HorizontalAlignment = 'right'; + app.NozzleDischargeCoefficientEditFieldLabel.Position = [1 9 159 22]; + app.NozzleDischargeCoefficientEditFieldLabel.Text = 'Nozzle Discharge Coefficient'; + + % Create NozzleCd + app.NozzleCd = uieditfield(app.NozzleConfigurationPanel, 'numeric'); + app.NozzleCd.Limits = [0 1]; + app.NozzleCd.Position = [230 9 100 22]; + app.NozzleCd.Value = 1; + + % Create noz_thrt_unit + app.noz_thrt_unit = uidropdown(app.NozzleConfigurationPanel); + app.noz_thrt_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.noz_thrt_unit.Editable = 'on'; + app.noz_thrt_unit.BackgroundColor = [1 1 1]; + app.noz_thrt_unit.Position = [329 99 60 22]; + app.noz_thrt_unit.Value = 'cm'; + + % Create noz_percent + app.noz_percent = uilabel(app.NozzleConfigurationPanel); + app.noz_percent.HorizontalAlignment = 'center'; + app.noz_percent.Position = [329 39 25 22]; + app.noz_percent.Text = '%'; + + % Create noz_ext_unit + app.noz_ext_unit = uidropdown(app.NozzleConfigurationPanel); + app.noz_ext_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.noz_ext_unit.Editable = 'on'; + app.noz_ext_unit.Visible = 'off'; + app.noz_ext_unit.BackgroundColor = [1 1 1]; + app.noz_ext_unit.Position = [329 69 60 22]; + app.noz_ext_unit.Value = 'in'; + + % Create NozzleDropDown + app.NozzleDropDown = uidropdown(app.NozzleConfigurationPanel); + app.NozzleDropDown.Items = {'Nozzle Expansion Ratio', 'Nozzle Exit Diameter'}; + app.NozzleDropDown.ValueChangedFcn = createCallbackFcn(app, @noz_definition, true); + app.NozzleDropDown.Position = [5 69 171 22]; + app.NozzleDropDown.Value = 'Nozzle Expansion Ratio'; + + % Create PropellantConfigurationPanel + app.PropellantConfigurationPanel = uipanel(app.MotorTab); + app.PropellantConfigurationPanel.Title = 'Propellant Configuration'; + app.PropellantConfigurationPanel.Position = [421 435 400 360]; + + % Create PropellantNameEditFieldLabel + app.PropellantNameEditFieldLabel = uilabel(app.PropellantConfigurationPanel); + app.PropellantNameEditFieldLabel.HorizontalAlignment = 'right'; + app.PropellantNameEditFieldLabel.Position = [1 278 95 22]; + app.PropellantNameEditFieldLabel.Text = 'Propellant Name'; + + % Create PropellantName + app.PropellantName = uieditfield(app.PropellantConfigurationPanel, 'text'); + app.PropellantName.Editable = 'off'; + app.PropellantName.Position = [215 278 106 22]; + + % Create PropellantDensityEditFieldLabel + app.PropellantDensityEditFieldLabel = uilabel(app.PropellantConfigurationPanel); + app.PropellantDensityEditFieldLabel.HorizontalAlignment = 'right'; + app.PropellantDensityEditFieldLabel.Position = [1 248 103 22]; + app.PropellantDensityEditFieldLabel.Text = 'Propellant Density'; + + % Create PropellantDensity + app.PropellantDensity = uieditfield(app.PropellantConfigurationPanel, 'numeric'); + app.PropellantDensity.Limits = [0 Inf]; + app.PropellantDensity.Position = [215 248 106 22]; + app.PropellantDensity.Value = 1000; + + % Create RegressionCoefficientaEditFieldLabel + app.RegressionCoefficientaEditFieldLabel = uilabel(app.PropellantConfigurationPanel); + app.RegressionCoefficientaEditFieldLabel.HorizontalAlignment = 'right'; + app.RegressionCoefficientaEditFieldLabel.Enable = 'off'; + app.RegressionCoefficientaEditFieldLabel.Position = [1 218 144 22]; + app.RegressionCoefficientaEditFieldLabel.Text = 'Regression Coefficient (a)'; + + % Create RegressionCoefficient + app.RegressionCoefficient = uieditfield(app.PropellantConfigurationPanel, 'numeric'); + app.RegressionCoefficient.Limits = [0 Inf]; + app.RegressionCoefficient.Editable = 'off'; + app.RegressionCoefficient.Enable = 'off'; + app.RegressionCoefficient.Position = [215 218 106 22]; + + % Create RegressionExponentnEditFieldLabel + app.RegressionExponentnEditFieldLabel = uilabel(app.PropellantConfigurationPanel); + app.RegressionExponentnEditFieldLabel.HorizontalAlignment = 'right'; + app.RegressionExponentnEditFieldLabel.Enable = 'off'; + app.RegressionExponentnEditFieldLabel.Position = [2 189 138 22]; + app.RegressionExponentnEditFieldLabel.Text = 'Regression Exponent (n)'; + + % Create RegressionExponent + app.RegressionExponent = uieditfield(app.PropellantConfigurationPanel, 'numeric'); + app.RegressionExponent.Editable = 'off'; + app.RegressionExponent.Enable = 'off'; + app.RegressionExponent.Position = [215 189 106 22]; + + % Create LengthExponentmEditFieldLabel + app.LengthExponentmEditFieldLabel = uilabel(app.PropellantConfigurationPanel); + app.LengthExponentmEditFieldLabel.HorizontalAlignment = 'right'; + app.LengthExponentmEditFieldLabel.Enable = 'off'; + app.LengthExponentmEditFieldLabel.Position = [2 159 118 22]; + app.LengthExponentmEditFieldLabel.Text = 'Length Exponent (m)'; + + % Create LengthExponent + app.LengthExponent = uieditfield(app.PropellantConfigurationPanel, 'numeric'); + app.LengthExponent.Editable = 'off'; + app.LengthExponent.Enable = 'off'; + app.LengthExponent.Position = [215 159 106 22]; + + % Create CEfficiencyEditFieldLabel + app.CEfficiencyEditFieldLabel = uilabel(app.PropellantConfigurationPanel); + app.CEfficiencyEditFieldLabel.HorizontalAlignment = 'right'; + app.CEfficiencyEditFieldLabel.Position = [4 98 73 22]; + app.CEfficiencyEditFieldLabel.Text = 'C* Efficiency'; + + % Create CEfficiency + app.CEfficiency = uieditfield(app.PropellantConfigurationPanel, 'numeric'); + app.CEfficiency.Limits = [0 100]; + app.CEfficiency.Position = [215 98 106 22]; + app.CEfficiency.Value = 100; + + % Create GrainIDEditFieldLabel + app.GrainIDEditFieldLabel = uilabel(app.PropellantConfigurationPanel); + app.GrainIDEditFieldLabel.HorizontalAlignment = 'right'; + app.GrainIDEditFieldLabel.Position = [4 68 50 22]; + app.GrainIDEditFieldLabel.Text = 'Grain ID'; + + % Create GrainID + app.GrainID = uieditfield(app.PropellantConfigurationPanel, 'numeric'); + app.GrainID.Limits = [0 Inf]; + app.GrainID.Position = [215 68 106 22]; + + % Create GrainODEditFieldLabel + app.GrainODEditFieldLabel = uilabel(app.PropellantConfigurationPanel); + app.GrainODEditFieldLabel.HorizontalAlignment = 'right'; + app.GrainODEditFieldLabel.Position = [4 38 56 22]; + app.GrainODEditFieldLabel.Text = 'Grain OD'; + + % Create GrainOD + app.GrainOD = uieditfield(app.PropellantConfigurationPanel, 'numeric'); + app.GrainOD.Limits = [0 Inf]; + app.GrainOD.Position = [215 38 106 22]; + + % Create GrainLengthEditFieldLabel + app.GrainLengthEditFieldLabel = uilabel(app.PropellantConfigurationPanel); + app.GrainLengthEditFieldLabel.HorizontalAlignment = 'right'; + app.GrainLengthEditFieldLabel.Position = [4 8 75 22]; + app.GrainLengthEditFieldLabel.Text = 'Grain Length'; + + % Create GrainLength + app.GrainLength = uieditfield(app.PropellantConfigurationPanel, 'numeric'); + app.GrainLength.Limits = [0 Inf]; + app.GrainLength.Position = [215 8 106 22]; + + % Create LoadPropellantConfig + app.LoadPropellantConfig = uibutton(app.PropellantConfigurationPanel, 'push'); + app.LoadPropellantConfig.ButtonPushedFcn = createCallbackFcn(app, @load_propellant, true); + app.LoadPropellantConfig.Position = [108 312 174 22]; + app.LoadPropellantConfig.Text = 'Load Propellant Configuration'; + + % Create ConstantOFRatioLabel + app.ConstantOFRatioLabel = uilabel(app.PropellantConfigurationPanel); + app.ConstantOFRatioLabel.HorizontalAlignment = 'right'; + app.ConstantOFRatioLabel.Position = [4 128 105 22]; + app.ConstantOFRatioLabel.Text = 'Constant OF Ratio'; + + % Create ConstantOF + app.ConstantOF = uieditfield(app.PropellantConfigurationPanel, 'numeric'); + app.ConstantOF.Limits = [0 Inf]; + app.ConstantOF.Position = [215 128 106 22]; + + % Create cstar_percent + app.cstar_percent = uilabel(app.PropellantConfigurationPanel); + app.cstar_percent.HorizontalAlignment = 'center'; + app.cstar_percent.Position = [320 98 25 22]; + app.cstar_percent.Text = '%'; + + % Create density_unit + app.density_unit = uidropdown(app.PropellantConfigurationPanel); + app.density_unit.Items = {'lb/in^3', 'lb/ft^3', 'g/cm^3', 'kg/m^3'}; + app.density_unit.Editable = 'on'; + app.density_unit.BackgroundColor = [1 1 1]; + app.density_unit.Position = [320 248 75 22]; + app.density_unit.Value = 'kg/m^3'; + + % Create grnID_unit + app.grnID_unit = uidropdown(app.PropellantConfigurationPanel); + app.grnID_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.grnID_unit.Editable = 'on'; + app.grnID_unit.BackgroundColor = [1 1 1]; + app.grnID_unit.Position = [319 68 75 22]; + app.grnID_unit.Value = 'cm'; + + % Create grnOD_unit + app.grnOD_unit = uidropdown(app.PropellantConfigurationPanel); + app.grnOD_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.grnOD_unit.Editable = 'on'; + app.grnOD_unit.BackgroundColor = [1 1 1]; + app.grnOD_unit.Position = [319 38 75 22]; + app.grnOD_unit.Value = 'cm'; + + % Create grnL_unit + app.grnL_unit = uidropdown(app.PropellantConfigurationPanel); + app.grnL_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.grnL_unit.Editable = 'on'; + app.grnL_unit.BackgroundColor = [1 1 1]; + app.grnL_unit.Position = [319 8 75 22]; + app.grnL_unit.Value = 'cm'; + + % Create a_unit + app.a_unit = uilabel(app.PropellantConfigurationPanel); + app.a_unit.Enable = 'off'; + app.a_unit.Position = [320 218 38 22]; + app.a_unit.Text = ' mm/s'; + + % Create n_unit + app.n_unit = uilabel(app.PropellantConfigurationPanel); + app.n_unit.Enable = 'off'; + app.n_unit.Position = [320 189 57 22]; + app.n_unit.Text = ' kg/m^2-s'; + + % Create m_unit + app.m_unit = uilabel(app.PropellantConfigurationPanel); + app.m_unit.Enable = 'off'; + app.m_unit.Position = [320 159 25 22]; + app.m_unit.Text = ' m'; + + % Create InjectorConfigurationPanel + app.InjectorConfigurationPanel = uipanel(app.MotorTab); + app.InjectorConfigurationPanel.Title = 'Injector Configuration'; + app.InjectorConfigurationPanel.Position = [421 216 398 210]; + + % Create InjectorDiameterEditFieldLabel + app.InjectorDiameterEditFieldLabel = uilabel(app.InjectorConfigurationPanel); + app.InjectorDiameterEditFieldLabel.HorizontalAlignment = 'right'; + app.InjectorDiameterEditFieldLabel.Position = [1 158 97 22]; + app.InjectorDiameterEditFieldLabel.Text = 'Injector Diameter'; + + % Create InjectorDiameter + app.InjectorDiameter = uieditfield(app.InjectorConfigurationPanel, 'numeric'); + app.InjectorDiameter.Limits = [0 Inf]; + app.InjectorDiameter.Position = [215 158 105 22]; + + % Create InjectorDischargeCoefficientEditFieldLabel + app.InjectorDischargeCoefficientEditFieldLabel = uilabel(app.InjectorConfigurationPanel); + app.InjectorDischargeCoefficientEditFieldLabel.HorizontalAlignment = 'right'; + app.InjectorDischargeCoefficientEditFieldLabel.Position = [1 128 162 22]; + app.InjectorDischargeCoefficientEditFieldLabel.Text = 'Injector Discharge Coefficient'; + + % Create InjectorCd + app.InjectorCd = uieditfield(app.InjectorConfigurationPanel, 'numeric'); + app.InjectorCd.Limits = [0 1]; + app.InjectorCd.Position = [215 128 105 22]; + app.InjectorCd.Value = 1; + + % Create NumberofInjectorsEditFieldLabel + app.NumberofInjectorsEditFieldLabel = uilabel(app.InjectorConfigurationPanel); + app.NumberofInjectorsEditFieldLabel.HorizontalAlignment = 'right'; + app.NumberofInjectorsEditFieldLabel.Position = [1 98 110 22]; + app.NumberofInjectorsEditFieldLabel.Text = 'Number of Injectors'; + + % Create NumberofInjectors + app.NumberofInjectors = uieditfield(app.InjectorConfigurationPanel, 'numeric'); + app.NumberofInjectors.Limits = [1 Inf]; + app.NumberofInjectors.RoundFractionalValues = 'on'; + app.NumberofInjectors.ValueDisplayFormat = '%.0f'; + app.NumberofInjectors.Position = [215 98 105 22]; + app.NumberofInjectors.Value = 1; + + % Create VentDiameterEditFieldLabel + app.VentDiameterEditFieldLabel = uilabel(app.InjectorConfigurationPanel); + app.VentDiameterEditFieldLabel.HorizontalAlignment = 'right'; + app.VentDiameterEditFieldLabel.Enable = 'off'; + app.VentDiameterEditFieldLabel.Position = [1 38 82 22]; + app.VentDiameterEditFieldLabel.Text = 'Vent Diameter'; + + % Create VentDiameter + app.VentDiameter = uieditfield(app.InjectorConfigurationPanel, 'numeric'); + app.VentDiameter.Limits = [0 Inf]; + app.VentDiameter.Editable = 'off'; + app.VentDiameter.Enable = 'off'; + app.VentDiameter.Position = [215 38 105 22]; + + % Create VentDischargeCoefficientEditFieldLabel + app.VentDischargeCoefficientEditFieldLabel = uilabel(app.InjectorConfigurationPanel); + app.VentDischargeCoefficientEditFieldLabel.HorizontalAlignment = 'right'; + app.VentDischargeCoefficientEditFieldLabel.Enable = 'off'; + app.VentDischargeCoefficientEditFieldLabel.Position = [1 8 147 22]; + app.VentDischargeCoefficientEditFieldLabel.Text = 'Vent Discharge Coefficient'; + + % Create VentCd + app.VentCd = uieditfield(app.InjectorConfigurationPanel, 'numeric'); + app.VentCd.Limits = [0 1]; + app.VentCd.Editable = 'off'; + app.VentCd.Enable = 'off'; + app.VentCd.Position = [215 8 105 22]; + + % Create VentStateDropDownLabel + app.VentStateDropDownLabel = uilabel(app.InjectorConfigurationPanel); + app.VentStateDropDownLabel.HorizontalAlignment = 'right'; + app.VentStateDropDownLabel.Position = [1 68 61 22]; + app.VentStateDropDownLabel.Text = 'Vent State'; + + % Create VentState + app.VentState = uidropdown(app.InjectorConfigurationPanel); + app.VentState.Items = {'None', 'External', 'Internal'}; + app.VentState.ValueChangedFcn = createCallbackFcn(app, @vent_state, true); + app.VentState.Position = [215 68 105 22]; + app.VentState.Value = 'None'; + + % Create vnt_D_unit + app.vnt_D_unit = uidropdown(app.InjectorConfigurationPanel); + app.vnt_D_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.vnt_D_unit.Enable = 'off'; + app.vnt_D_unit.Position = [319 38 75 22]; + app.vnt_D_unit.Value = 'cm'; + + % Create inj_D_unit + app.inj_D_unit = uidropdown(app.InjectorConfigurationPanel); + app.inj_D_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.inj_D_unit.Editable = 'on'; + app.inj_D_unit.BackgroundColor = [1 1 1]; + app.inj_D_unit.Position = [319 158 75 22]; + app.inj_D_unit.Value = 'cm'; + + % Create MassPropertiesPanel + app.MassPropertiesPanel = uipanel(app.MotorTab); + app.MassPropertiesPanel.Title = 'Mass Properties'; + app.MassPropertiesPanel.Position = [11 216 401 180]; + + % Create OxidizerTankLocationEditFieldLabel + app.OxidizerTankLocationEditFieldLabel = uilabel(app.MassPropertiesPanel); + app.OxidizerTankLocationEditFieldLabel.HorizontalAlignment = 'right'; + app.OxidizerTankLocationEditFieldLabel.Enable = 'off'; + app.OxidizerTankLocationEditFieldLabel.Position = [5 127 127 22]; + app.OxidizerTankLocationEditFieldLabel.Text = 'Oxidizer Tank Location'; + + % Create TankLocation + app.TankLocation = uieditfield(app.MassPropertiesPanel, 'numeric'); + app.TankLocation.Limits = [0 Inf]; + app.TankLocation.Editable = 'off'; + app.TankLocation.Enable = 'off'; + app.TankLocation.Position = [234 127 96 22]; + + % Create FuelGrainLocationEditFieldLabel + app.FuelGrainLocationEditFieldLabel = uilabel(app.MassPropertiesPanel); + app.FuelGrainLocationEditFieldLabel.HorizontalAlignment = 'right'; + app.FuelGrainLocationEditFieldLabel.Enable = 'off'; + app.FuelGrainLocationEditFieldLabel.Position = [5 98 110 22]; + app.FuelGrainLocationEditFieldLabel.Text = 'Fuel Grain Location'; + + % Create GrainLocation + app.GrainLocation = uieditfield(app.MassPropertiesPanel, 'numeric'); + app.GrainLocation.Limits = [0 Inf]; + app.GrainLocation.Editable = 'off'; + app.GrainLocation.Enable = 'off'; + app.GrainLocation.Position = [234 98 96 22]; + + % Create EmptyMotorCenterofMassEditFieldLabel + app.EmptyMotorCenterofMassEditFieldLabel = uilabel(app.MassPropertiesPanel); + app.EmptyMotorCenterofMassEditFieldLabel.HorizontalAlignment = 'right'; + app.EmptyMotorCenterofMassEditFieldLabel.Enable = 'off'; + app.EmptyMotorCenterofMassEditFieldLabel.Position = [5 68 158 22]; + app.EmptyMotorCenterofMassEditFieldLabel.Text = 'Empty Motor Center of Mass'; + + % Create MotorCG + app.MotorCG = uieditfield(app.MassPropertiesPanel, 'numeric'); + app.MotorCG.Limits = [0 Inf]; + app.MotorCG.Editable = 'off'; + app.MotorCG.Enable = 'off'; + app.MotorCG.Position = [234 68 96 22]; + + % Create EmptyMotorMassEditFieldLabel + app.EmptyMotorMassEditFieldLabel = uilabel(app.MassPropertiesPanel); + app.EmptyMotorMassEditFieldLabel.HorizontalAlignment = 'right'; + app.EmptyMotorMassEditFieldLabel.Enable = 'off'; + app.EmptyMotorMassEditFieldLabel.Position = [5 38 105 22]; + app.EmptyMotorMassEditFieldLabel.Text = 'Empty Motor Mass'; + + % Create MotorMass + app.MotorMass = uieditfield(app.MassPropertiesPanel, 'numeric'); + app.MotorMass.Limits = [0 Inf]; + app.MotorMass.Editable = 'off'; + app.MotorMass.Enable = 'off'; + app.MotorMass.Position = [234 38 96 22]; + + % Create MassProperties + app.MassProperties = uicheckbox(app.MassPropertiesPanel); + app.MassProperties.ValueChangedFcn = createCallbackFcn(app, @enable_mass_properties, true); + app.MassProperties.Text = 'Calculate Mass Properties'; + app.MassProperties.Position = [21 8 269 22]; + + % Create tnk_X_unit + app.tnk_X_unit = uidropdown(app.MassPropertiesPanel); + app.tnk_X_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.tnk_X_unit.Enable = 'off'; + app.tnk_X_unit.Position = [329 127 60 22]; + app.tnk_X_unit.Value = 'cm'; + + % Create cmbr_X_unit + app.cmbr_X_unit = uidropdown(app.MassPropertiesPanel); + app.cmbr_X_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.cmbr_X_unit.Enable = 'off'; + app.cmbr_X_unit.Position = [329 98 60 22]; + app.cmbr_X_unit.Value = 'cm'; + + % Create mtr_cg_unit + app.mtr_cg_unit = uidropdown(app.MassPropertiesPanel); + app.mtr_cg_unit.Items = {'in', 'ft', 'mm', 'cm', 'm'}; + app.mtr_cg_unit.Enable = 'off'; + app.mtr_cg_unit.Position = [329 68 60 22]; + app.mtr_cg_unit.Value = 'cm'; + + % Create mtr_m_unit + app.mtr_m_unit = uidropdown(app.MassPropertiesPanel); + app.mtr_m_unit.Items = {'lbm', 'kg', 'g', 'oz'}; + app.mtr_m_unit.Enable = 'off'; + app.mtr_m_unit.Position = [329 38 60 22]; + app.mtr_m_unit.Value = 'kg'; + + % Create SimulationConfigurationPanel + app.SimulationConfigurationPanel = uipanel(app.MotorTab); + app.SimulationConfigurationPanel.Title = 'Simulation Configuration'; + app.SimulationConfigurationPanel.Position = [421 56 398 150]; + + % Create MaxSimulationRunTimeEditFieldLabel + app.MaxSimulationRunTimeEditFieldLabel = uilabel(app.SimulationConfigurationPanel); + app.MaxSimulationRunTimeEditFieldLabel.HorizontalAlignment = 'right'; + app.MaxSimulationRunTimeEditFieldLabel.Position = [10 99 142 22]; + app.MaxSimulationRunTimeEditFieldLabel.Text = 'Max Simulation Run Time'; + + % Create RunTime + app.RunTime = uieditfield(app.SimulationConfigurationPanel, 'numeric'); + app.RunTime.Limits = [0 Inf]; + app.RunTime.Position = [215 99 105 22]; + app.RunTime.Value = 10; + + % Create MaxBurnTimeEditFieldLabel + app.MaxBurnTimeEditFieldLabel = uilabel(app.SimulationConfigurationPanel); + app.MaxBurnTimeEditFieldLabel.HorizontalAlignment = 'right'; + app.MaxBurnTimeEditFieldLabel.Position = [10 70 86 22]; + app.MaxBurnTimeEditFieldLabel.Text = 'Max Burn Time'; + + % Create BurnTime + app.BurnTime = uieditfield(app.SimulationConfigurationPanel, 'numeric'); + app.BurnTime.Limits = [0 Inf]; + app.BurnTime.Position = [215 70 105 22]; + + % Create SimulationTimestepEditFieldLabel + app.SimulationTimestepEditFieldLabel = uilabel(app.SimulationConfigurationPanel); + app.SimulationTimestepEditFieldLabel.HorizontalAlignment = 'right'; + app.SimulationTimestepEditFieldLabel.Position = [10 40 114 22]; + app.SimulationTimestepEditFieldLabel.Text = 'Simulation Timestep'; + + % Create Timestep + app.Timestep = uieditfield(app.SimulationConfigurationPanel, 'numeric'); + app.Timestep.Limits = [0 Inf]; + app.Timestep.Position = [215 40 105 22]; + app.Timestep.Value = 0.001; + + % Create RegressionModelDropDownLabel + app.RegressionModelDropDownLabel = uilabel(app.SimulationConfigurationPanel); + app.RegressionModelDropDownLabel.HorizontalAlignment = 'right'; + app.RegressionModelDropDownLabel.Position = [10 9 102 22]; + app.RegressionModelDropDownLabel.Text = 'Regression Model'; + + % Create RegressionModel + app.RegressionModel = uidropdown(app.SimulationConfigurationPanel); + app.RegressionModel.Items = {'Constant OF', 'Shifting OF'}; + app.RegressionModel.ValueChangedFcn = createCallbackFcn(app, @reg_model, true); + app.RegressionModel.Position = [215 9 105 22]; + app.RegressionModel.Value = 'Constant OF'; + + % Create runtime_label + app.runtime_label = uilabel(app.SimulationConfigurationPanel); + app.runtime_label.HorizontalAlignment = 'center'; + app.runtime_label.Position = [319 99 25 22]; + app.runtime_label.Text = 's'; + + % Create burntime_label + app.burntime_label = uilabel(app.SimulationConfigurationPanel); + app.burntime_label.HorizontalAlignment = 'center'; + app.burntime_label.Position = [319 70 25 22]; + app.burntime_label.Text = 's'; + + % Create timestep_label + app.timestep_label = uilabel(app.SimulationConfigurationPanel); + app.timestep_label.HorizontalAlignment = 'center'; + app.timestep_label.Position = [319 40 25 22]; + app.timestep_label.Text = 's'; + + % Create InitialConditionsPanel + app.InitialConditionsPanel = uipanel(app.MotorTab); + app.InitialConditionsPanel.Title = 'Initial Conditions'; + app.InitialConditionsPanel.Position = [11 56 401 150]; + + % Create TankCond + app.TankCond = uieditfield(app.InitialConditionsPanel, 'numeric'); + app.TankCond.Limits = [0 Inf]; + app.TankCond.Position = [230 98 100 22]; + app.TankCond.Value = 293.15; + + % Create StartingChamberPressureEditFieldLabel + app.StartingChamberPressureEditFieldLabel = uilabel(app.InitialConditionsPanel); + app.StartingChamberPressureEditFieldLabel.HorizontalAlignment = 'right'; + app.StartingChamberPressureEditFieldLabel.Position = [5 69 151 22]; + app.StartingChamberPressureEditFieldLabel.Text = 'Starting Chamber Pressure'; + + % Create ChamberPressure + app.ChamberPressure = uieditfield(app.InitialConditionsPanel, 'numeric'); + app.ChamberPressure.Limits = [0 7251000]; + app.ChamberPressure.Position = [230 69 100 22]; + app.ChamberPressure.Value = 1; + + % Create OxidizerFill + app.OxidizerFill = uieditfield(app.InitialConditionsPanel, 'numeric'); + app.OxidizerFill.Limits = [0 Inf]; + app.OxidizerFill.ValueDisplayFormat = '%11.3g'; + app.OxidizerFill.Position = [230 39 100 22]; + + % Create AmbientPressureEditField_2Label + app.AmbientPressureEditField_2Label = uilabel(app.InitialConditionsPanel); + app.AmbientPressureEditField_2Label.HorizontalAlignment = 'right'; + app.AmbientPressureEditField_2Label.Position = [10 9 101 22]; + app.AmbientPressureEditField_2Label.Text = 'Ambient Pressure'; + + % Create AmbientPressure + app.AmbientPressure = uieditfield(app.InitialConditionsPanel, 'numeric'); + app.AmbientPressure.Limits = [0 Inf]; + app.AmbientPressure.Position = [230 9 100 22]; + app.AmbientPressure.Value = 1; + + % Create TankDropDown + app.TankDropDown = uidropdown(app.InitialConditionsPanel); + app.TankDropDown.Items = {'Starting Tank Temperature', 'Starting Tank Pressure'}; + app.TankDropDown.ValueChangedFcn = createCallbackFcn(app, @tank_condition, true); + app.TankDropDown.Position = [10 97 181 22]; + app.TankDropDown.Value = 'Starting Tank Temperature'; + + % Create OxidizerDropDown + app.OxidizerDropDown = uidropdown(app.InitialConditionsPanel); + app.OxidizerDropDown.Items = {'Starting Oxidizer Mass', 'Tank Fill Percentage'}; + app.OxidizerDropDown.ValueChangedFcn = createCallbackFcn(app, @tank_fill, true); + app.OxidizerDropDown.Position = [11 38 181 22]; + app.OxidizerDropDown.Value = 'Starting Oxidizer Mass'; + + % Create T_tnk_unit + app.T_tnk_unit = uidropdown(app.InitialConditionsPanel); + app.T_tnk_unit.Items = {'F', 'C', 'K', 'R'}; + app.T_tnk_unit.Editable = 'on'; + app.T_tnk_unit.BackgroundColor = [1 1 1]; + app.T_tnk_unit.Position = [329 98 60 22]; + app.T_tnk_unit.Value = 'K'; + + % Create P_cmbr_unit + app.P_cmbr_unit = uidropdown(app.InitialConditionsPanel); + app.P_cmbr_unit.Items = {'psi', 'psf', 'atm', 'Pa', 'kPa', 'Bar', 'MPa'}; + app.P_cmbr_unit.Editable = 'on'; + app.P_cmbr_unit.BackgroundColor = [1 1 1]; + app.P_cmbr_unit.Position = [329 69 60 22]; + app.P_cmbr_unit.Value = 'atm'; + + % Create Pa_unit + app.Pa_unit = uidropdown(app.InitialConditionsPanel); + app.Pa_unit.Items = {'psi', 'psf', 'atm', 'Pa', 'kPa', 'Bar', 'MPa'}; + app.Pa_unit.Editable = 'on'; + app.Pa_unit.BackgroundColor = [1 1 1]; + app.Pa_unit.Position = [329 9 60 22]; + app.Pa_unit.Value = 'atm'; + + % Create m_O_unit + app.m_O_unit = uidropdown(app.InitialConditionsPanel); + app.m_O_unit.Items = {'lbm', 'kg', 'g', 'oz'}; + app.m_O_unit.Editable = 'on'; + app.m_O_unit.BackgroundColor = [1 1 1]; + app.m_O_unit.Position = [329 39 60 22]; + app.m_O_unit.Value = 'kg'; + + % Create RunSimulation + app.RunSimulation = uibutton(app.MotorTab, 'push'); + app.RunSimulation.ButtonPushedFcn = createCallbackFcn(app, @run_sim, true); + app.RunSimulation.BackgroundColor = [0.9608 0.9608 0.9608]; + app.RunSimulation.FontSize = 24; + app.RunSimulation.Position = [331 9 178 37]; + app.RunSimulation.Text = 'Run Simulation'; + + % Create LoadMotor + app.LoadMotor = uibutton(app.MotorTab, 'push'); + app.LoadMotor.ButtonPushedFcn = createCallbackFcn(app, @load_config, true); + app.LoadMotor.Position = [11 24 176 22]; + app.LoadMotor.Text = 'Load Simulation Configuration'; + + % Create SaveMotor + app.SaveMotor = uibutton(app.MotorTab, 'push'); + app.SaveMotor.ButtonPushedFcn = createCallbackFcn(app, @save_config, true); + app.SaveMotor.Position = [641 24 176 22]; + app.SaveMotor.Text = 'Save Simulation Configuration'; + + % Create ResultsTab + app.ResultsTab = uitab(app.AppTabs); + app.ResultsTab.Title = 'Results'; + + % Create UIAxes + app.UIAxes = uiaxes(app.ResultsTab); + title(app.UIAxes, 'Thrust vs Time') + app.UIAxes.XTickLabelRotation = 0; + app.UIAxes.YTickLabelRotation = 0; + app.UIAxes.ZTickLabelRotation = 0; + app.UIAxes.XGrid = 'on'; + app.UIAxes.YGrid = 'on'; + app.UIAxes.Position = [11 265 810 535]; + + % Create XaxisDropDownLabel + app.XaxisDropDownLabel = uilabel(app.ResultsTab); + app.XaxisDropDownLabel.HorizontalAlignment = 'right'; + app.XaxisDropDownLabel.Enable = 'off'; + app.XaxisDropDownLabel.Position = [451 194 39 22]; + app.XaxisDropDownLabel.Text = 'X-axis'; + + % Create Xaxis + app.Xaxis = uidropdown(app.ResultsTab); + app.Xaxis.Items = {'Time', 'Oxidizer Mass', 'Fuel Mass', 'Total Motor Mass', 'Center of Mass', 'Chamber Pressure', 'Total Propellant Mass', 'Tank Pressure', 'Oxidizer Mass Flow Rate', 'Fuel Mass Flow Rate', 'Total Mass Flow Rate', 'OF Ratio', 'Regression Rate', 'Thrust', 'Injector Pressure Drop'}; + app.Xaxis.ValueChangedFcn = createCallbackFcn(app, @x_axis, true); + app.Xaxis.Enable = 'off'; + app.Xaxis.Position = [636 194 180 22]; + app.Xaxis.Value = 'Time'; + + % Create PlotTitleEditFieldLabel + app.PlotTitleEditFieldLabel = uilabel(app.ResultsTab); + app.PlotTitleEditFieldLabel.HorizontalAlignment = 'right'; + app.PlotTitleEditFieldLabel.Enable = 'off'; + app.PlotTitleEditFieldLabel.Position = [453 223 52 22]; + app.PlotTitleEditFieldLabel.Text = 'Plot Title'; + + % Create PlotTitle + app.PlotTitle = uieditfield(app.ResultsTab, 'text'); + app.PlotTitle.Editable = 'off'; + app.PlotTitle.Enable = 'off'; + app.PlotTitle.Position = [513 223 305 22]; + app.PlotTitle.Value = 'Thrust vs Time'; + + % Create YaxisDropDownLabel + app.YaxisDropDownLabel = uilabel(app.ResultsTab); + app.YaxisDropDownLabel.HorizontalAlignment = 'right'; + app.YaxisDropDownLabel.Enable = 'off'; + app.YaxisDropDownLabel.Position = [451 164 38 22]; + app.YaxisDropDownLabel.Text = 'Y-axis'; + + % Create Yaxis + app.Yaxis = uidropdown(app.ResultsTab); + app.Yaxis.Items = {'Time', 'Oxidizer Mass', 'Fuel Mass', 'Total Propellant Mass', 'Total Motor Mass', 'Center of Mass', 'Chamber Pressure', 'Tank Pressure', 'Oxidizer Mass Flow Rate', 'Fuel Mass Flow Rate', 'Total Mass Flow Rate', 'OF Ratio', 'Regression Rate', 'Thrust', 'Injector Pressure Drop'}; + app.Yaxis.ValueChangedFcn = createCallbackFcn(app, @y_axis, true); + app.Yaxis.Enable = 'off'; + app.Yaxis.Position = [636 164 180 22]; + app.Yaxis.Value = 'Thrust'; + + % Create PerformanceSummaryTextAreaLabel + app.PerformanceSummaryTextAreaLabel = uilabel(app.ResultsTab); + app.PerformanceSummaryTextAreaLabel.BackgroundColor = [0.9412 0.9412 0.9412]; + app.PerformanceSummaryTextAreaLabel.HorizontalAlignment = 'right'; + app.PerformanceSummaryTextAreaLabel.Position = [152 250 129 22]; + app.PerformanceSummaryTextAreaLabel.Text = 'Performance Summary'; + + % Create PerformanceSummary + app.PerformanceSummary = uitextarea(app.ResultsTab); + app.PerformanceSummary.Editable = 'off'; + app.PerformanceSummary.BackgroundColor = [0.8 0.8 0.8]; + app.PerformanceSummary.Position = [12 6 410 246]; + app.PerformanceSummary.Value = {'You must first run a simulation to view results or plot data'}; + + % Create AddPlot + app.AddPlot = uibutton(app.ResultsTab, 'push'); + app.AddPlot.ButtonPushedFcn = createCallbackFcn(app, @plot, true); + app.AddPlot.Enable = 'off'; + app.AddPlot.Position = [450 35 100 22]; + app.AddPlot.Text = 'Add Plot'; + + % Create ClearPlot + app.ClearPlot = uibutton(app.ResultsTab, 'push'); + app.ClearPlot.ButtonPushedFcn = createCallbackFcn(app, @clear_plot, true); + app.ClearPlot.Enable = 'off'; + app.ClearPlot.Position = [717 35 100 22]; + app.ClearPlot.Text = 'Clear Plot'; + + % Create SavePlot + app.SavePlot = uibutton(app.ResultsTab, 'push'); + app.SavePlot.ButtonPushedFcn = createCallbackFcn(app, @save_plot, true); + app.SavePlot.Enable = 'off'; + app.SavePlot.Position = [583 35 100 22]; + app.SavePlot.Text = 'Save Plot'; + + % Create SaveResults + app.SaveResults = uibutton(app.ResultsTab, 'push'); + app.SaveResults.ButtonPushedFcn = createCallbackFcn(app, @save_results, true); + app.SaveResults.Enable = 'off'; + app.SaveResults.Position = [717 5 100 22]; + app.SaveResults.Text = 'Save Results'; + + % Create ExportRSE + app.ExportRSE = uibutton(app.ResultsTab, 'push'); + app.ExportRSE.ButtonPushedFcn = createCallbackFcn(app, @save_rse, true); + app.ExportRSE.Enable = 'off'; + app.ExportRSE.Position = [450 5 100 22]; + app.ExportRSE.Text = 'Export .RSE'; + + % Create ExportCSV + app.ExportCSV = uibutton(app.ResultsTab, 'push'); + app.ExportCSV.ButtonPushedFcn = createCallbackFcn(app, @save_csv, true); + app.ExportCSV.Enable = 'off'; + app.ExportCSV.Position = [583 5 100 22]; + app.ExportCSV.Text = 'Export .CSV'; + + % Create XaxisUnitsDropDownLabel + app.XaxisUnitsDropDownLabel = uilabel(app.ResultsTab); + app.XaxisUnitsDropDownLabel.HorizontalAlignment = 'right'; + app.XaxisUnitsDropDownLabel.Enable = 'off'; + app.XaxisUnitsDropDownLabel.Position = [451 134 70 22]; + app.XaxisUnitsDropDownLabel.Text = 'X-axis Units'; + + % Create XaxisUnits + app.XaxisUnits = uidropdown(app.ResultsTab); + app.XaxisUnits.Items = {'s'}; + app.XaxisUnits.Enable = 'off'; + app.XaxisUnits.Position = [636 134 180 22]; + app.XaxisUnits.Value = 's'; + + % Create YaxisUnitsDropDownLabel + app.YaxisUnitsDropDownLabel = uilabel(app.ResultsTab); + app.YaxisUnitsDropDownLabel.HorizontalAlignment = 'right'; + app.YaxisUnitsDropDownLabel.Enable = 'off'; + app.YaxisUnitsDropDownLabel.Position = [452 104 69 22]; + app.YaxisUnitsDropDownLabel.Text = 'Y-axis Units'; + + % Create YaxisUnits + app.YaxisUnits = uidropdown(app.ResultsTab); + app.YaxisUnits.Items = {'N', 'lbf', 'kgf'}; + app.YaxisUnits.Enable = 'off'; + app.YaxisUnits.Position = [636 104 180 22]; + app.YaxisUnits.Value = 'N'; + + % Create LegendEditFieldLabel + app.LegendEditFieldLabel = uilabel(app.ResultsTab); + app.LegendEditFieldLabel.HorizontalAlignment = 'right'; + app.LegendEditFieldLabel.Enable = 'off'; + app.LegendEditFieldLabel.Position = [453 73 46 22]; + app.LegendEditFieldLabel.Text = 'Legend'; + + % Create Legend + app.Legend = uieditfield(app.ResultsTab, 'text'); + app.Legend.Editable = 'off'; + app.Legend.Enable = 'off'; + app.Legend.Position = [513 73 305 22]; + app.Legend.Value = 'Thrust'; + + % Create AboutTab + app.AboutTab = uitab(app.AppTabs); + app.AboutTab.Title = 'About'; + + % Create TextArea + app.TextArea = uitextarea(app.AboutTab); + app.TextArea.Editable = 'off'; + app.TextArea.BackgroundColor = [0.8 0.8 0.8]; + app.TextArea.Position = [11 9 810 786]; + app.TextArea.Value = {'The Hybrid Rocket Analysis Program (HRAP) was developed by Robert (Drew) Nickel for use by the University of Tennessee Rocket Engineering Team, part of the Student Space Technology Association at UTK. HRAP is a versatile tool utilizing a thermodynamic equilibrium model for simulation of self-pressurizing hybrid rocket motors, especially those powered with Nitrous Oxide stored as a saturated liquid-vapor mixture. HRAP models all phases of a typical nitrous oxide hybrid rocket burn, with an equilibrium tank model, a transient chamber pressure model, options for both constant OF and shifting OF regression models, an isentropic nozzle model, and a simple but effective mass properties model. This program can be used to model flight motors burning to tank depletion or boilerplate motors with a set burn time, and can generate and export all data and plots as well as compile a .RSE file for use in flight simulations, future iterations will account for subsonic flow and flow separation to better model thrust, non-equilibrium tank models, and two phase injector flow. For an in-depth look at how HRAP works, there is a document linked below. For bug reports or suggestions, send a screenshot, explanation and any additional details to: rnickel1@vols.utk.edu'; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; ''; 'Acknowledgements: I would like to thank Dr Richard Newlands with Aspire Space for his extensive help in the development of HRAP. His hybrid rocket model laid the foundation for HRAP, and I could not have gotten this far without him. I would like to thank James Anderson with Equatorial Space Industries in Singapore for providing test data for model verification. Lastly, I would like to thank Dr. Evans Lyne, Dr. Kuvanc Ekici, Dr. Mark Barker, Dr. Matthew Mench, and Dr. Robert Jacobsen from the University of Tennessee, Thomas Sanders from Contrail Rockets LLC, and James Hudspeth from Imperia Aerospace for their academic and/or financial support of this endeavor.'}; + + % Create ClickhereformoreinformationregardingHRAPitsusageandButton + app.ClickhereformoreinformationregardingHRAPitsusageandButton = uibutton(app.AboutTab, 'push'); + app.ClickhereformoreinformationregardingHRAPitsusageandButton.ButtonPushedFcn = createCallbackFcn(app, @open_hrap_document, true); + app.ClickhereformoreinformationregardingHRAPitsusageandButton.Position = [221 134 414 22]; + app.ClickhereformoreinformationregardingHRAPitsusageandButton.Text = 'Click here for more information regarding HRAP, its usage, and validation.'; + + % Create Image + app.Image = uiimage(app.AboutTab); + app.Image.Position = [54 228 452 349]; + app.Image.ImageSource = 'test fire.jpg'; + + % Create Label + app.Label = uilabel(app.AboutTab); + app.Label.Position = [43 186 770 22]; + app.Label.Text = 'Image: University of Tennessee 3,000 Newton Research Hybrid (left) and 5,000 Newton sounding rocket (right), both developed using HRAP.'; + + % Create Image2 + app.Image2 = uiimage(app.AboutTab); + app.Image2.Position = [540 229 234 349]; + app.Image2.ImageSource = 'Launch.jpg'; + + % Create AuthorLabel + app.AuthorLabel = uilabel(app.HRAP_v2022_06_04); + app.AuthorLabel.Position = [711 -1 130 22]; + app.AuthorLabel.Text = 'Robert A. Nickel - 2023'; + + % Create ReleaseLabel + app.ReleaseLabel = uilabel(app.HRAP_v2022_06_04); + app.ReleaseLabel.Position = [12 -1 114 22]; + app.ReleaseLabel.Text = 'Release 2023-07-14'; + + % Show the figure after all components are created + app.HRAP_v2022_06_04.Visible = 'on'; + end + end + + % App creation and deletion + methods (Access = public) + + % Construct app + function app = HRAP + + % Create UIFigure and components + createComponents(app) + + % Register the app with App Designer + registerApp(app, app.HRAP_v2022_06_04) + + % Execute the startup function + runStartupFcn(app, @startupFcn) + + if nargout == 0 + clear app + end + end + + % Code that executes before app deletion + function delete(app) + + % Delete UIFigure when app is deleted + delete(app.HRAP_v2022_06_04) + end + end +end \ No newline at end of file diff --git a/HRAP - Python/hrap_python/demo.py b/HRAP - Python/hrap_python/demo.py new file mode 100644 index 0000000..1299655 --- /dev/null +++ b/HRAP - Python/hrap_python/demo.py @@ -0,0 +1,49 @@ +from api.core import sim_loop +from api.models import * +from api.sim import shift_OF +from util.units import * +import matplotlib.pyplot as plt + +material = MaterialData('propellants/Paraffin.npz') +prop_config = PropellantConfig(ID=LengthValue(2.0, LengthUnit.INCHES), + OD=LengthValue(4.0, LengthUnit.INCHES), + length=LengthValue(12.0, LengthUnit.INCHES), + cstar_eff=1.0, + material=material, + regression_model=shift_OF) + +hardware_config = HardwareConfig(tank_volume=VolumeValue(7000.0, VolumeUnit.CU_CENTIMETERS), + chamber_volume=None, + injector_Cd=0.6, + injector_D=LengthValue(0.375, LengthUnit.INCHES), + injector_N=1, + vent_state=VentConfig.EXTERNAL, + vent_Cd=0.6, + vent_D=LengthValue(0.028, LengthUnit.INCHES)) + +nozzle_config = NozzleConfig(Cd=1.0, + throat=LengthValue(1.2, LengthUnit.INCHES), + exp_ratio=4.0, + efficiency=1.0) + +sim_config = SimulationConfig(timestep=0.001, + max_run_time=10.0, + max_burn_time=10.0, + ambient_pressure=PressureValue(1.0, PressureUnit.ATM), + hardware=hardware_config, + prop=prop_config, + nozzle=nozzle_config) + +init_conditions = InitialConditions(chamber_pressure=PressureValue(1.0, PressureUnit.ATM), + mass_ox=MassValue(7.0, MassUnit.KILOGRAMS), + tank_temp=TemperatureValue(63.0, TemperatureUnit.FAHRENHEIT)) + +output = sim_loop(sim_config, init_conditions) + +time_data = output.time +thrust_data = [t.get_as(MassUnit.NEWTONS) for t in output.thrust] +plt.plot(time_data, thrust_data) +plt.xlabel('Time (s)') +plt.ylabel('Thrust (N)') +plt.title('Predicted Thrust') +plt.show() diff --git a/HRAP - Python/hrap_python/propellants/Paraffin.npz b/HRAP - Python/hrap_python/propellants/Paraffin.npz new file mode 100644 index 0000000..bb04fd3 Binary files /dev/null and b/HRAP - Python/hrap_python/propellants/Paraffin.npz differ diff --git a/HRAP - Python/hrap_python/propellants/convert.py b/HRAP - Python/hrap_python/propellants/convert.py new file mode 100644 index 0000000..b33856f --- /dev/null +++ b/HRAP - Python/hrap_python/propellants/convert.py @@ -0,0 +1,19 @@ +import numpy as np +from numpy import genfromtxt + +# Simple util for converting saved CSVs to bundled npz +# Run in the same folder as the exported CSVs, with filenames given below +# To generate CSVs from MAT files, use csvwrite in Matlab +OUTFILE = 'Paraffin.npz' +PROP_NAME = 'Paraffin' + +k = genfromtxt('k.csv', delimiter=',') +M = genfromtxt('M.csv', delimiter=',') +OF = genfromtxt('OF.csv', delimiter=',') +Pc = genfromtxt('Pc.csv', delimiter=',') +reg_coeff = genfromtxt('reg_coeff.csv', delimiter=',') +rho = genfromtxt('rho.csv', delimiter=',') +T = genfromtxt('T.csv', delimiter=',') + +np.savez(OUTFILE, metadata=[PROP_NAME, rho], OF=OF, Pc=Pc, k=k, M=M, T=T, regression_coeff=reg_coeff) + diff --git a/HRAP - Python/hrap_python/requirements.txt b/HRAP - Python/hrap_python/requirements.txt new file mode 100644 index 0000000..5576e19 --- /dev/null +++ b/HRAP - Python/hrap_python/requirements.txt @@ -0,0 +1,2 @@ +numpy +scipy \ No newline at end of file diff --git a/HRAP - Python/hrap_python/test/__init__.py b/HRAP - Python/hrap_python/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/HRAP - Python/hrap_python/test/test_chamber.py b/HRAP - Python/hrap_python/test/test_chamber.py new file mode 100644 index 0000000..961638a --- /dev/null +++ b/HRAP - Python/hrap_python/test/test_chamber.py @@ -0,0 +1 @@ +# TODO: Implement chamber tests \ No newline at end of file diff --git a/HRAP - Python/hrap_python/test/test_comb.py b/HRAP - Python/hrap_python/test/test_comb.py new file mode 100644 index 0000000..1d109b7 --- /dev/null +++ b/HRAP - Python/hrap_python/test/test_comb.py @@ -0,0 +1 @@ +# TODO: Implement combustion sim tests \ No newline at end of file diff --git a/HRAP - Python/hrap_python/test/test_const_OF.py b/HRAP - Python/hrap_python/test/test_const_OF.py new file mode 100644 index 0000000..6684f4c --- /dev/null +++ b/HRAP - Python/hrap_python/test/test_const_OF.py @@ -0,0 +1 @@ +# TODO: Implement const-OF regression sim tests \ No newline at end of file diff --git a/HRAP - Python/hrap_python/test/test_nox.py b/HRAP - Python/hrap_python/test/test_nox.py new file mode 100644 index 0000000..3a20a2b --- /dev/null +++ b/HRAP - Python/hrap_python/test/test_nox.py @@ -0,0 +1,43 @@ +from util.nox import * +import unittest + +class TestNOXOutputs(unittest.TestCase): + TEMPLATE = "Value %s is not within acceptable bounds at temperature %.2fK!" + def test_nominal_value(self): + T = 273.15 + props = get_NOX_properties(T) + self.assertAlmostEqual(props.Pv, 3.1266e6, delta=10) + self.assertAlmostEqual(props.rho_l, 907.407, delta=1e-3) + self.assertAlmostEqual(props.rho_v, 84.8624, delta=1e-4) + self.assertAlmostEqual(props.Hv, 232.19, delta=1e-3) + self.assertAlmostEqual(props.Cp, 2.2741, delta=1e-5) + self.assertAlmostEqual(props.Z, 0.714004, delta=1e-6) + + def test_max_value(self): + T = 309.0 + props = get_NOX_properties(T) + self.assertAlmostEqual(props.Pv, 7.162454e6, delta=10) + self.assertAlmostEqual(props.rho_l, 551.81, delta=1e-3) + self.assertAlmostEqual(props.rho_v, 367.76, delta=1e-3) + self.assertAlmostEqual(props.Hv, 45.9456, delta=1e-4) + self.assertAlmostEqual(props.Cp, 34.3239, delta=1e-4) + self.assertAlmostEqual(props.Z, 0.333644, delta=1e-6) + + def test_min_value(self): + T = 183.15 + props = get_NOX_properties(T) + self.assertAlmostEqual(props.Pv, 9.2287e4, delta=10) + self.assertAlmostEqual(props.rho_l, 1220.6, delta=1) + self.assertAlmostEqual(props.rho_v, 2.73844, delta=1e-5) + self.assertAlmostEqual(props.Hv, 376.51, delta=1e-3) + self.assertAlmostEqual(props.Cp, 1.75001, delta=1e-5) + self.assertAlmostEqual(props.Z, 0.974032, delta=1e-6) + + def test_invalid_values(self): + with self.assertRaises(ValueError): + get_NOX_properties(310) + with self.assertRaises(ValueError): + get_NOX_properties(183) + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/HRAP - Python/hrap_python/test/test_nozzle.py b/HRAP - Python/hrap_python/test/test_nozzle.py new file mode 100644 index 0000000..4c768b5 --- /dev/null +++ b/HRAP - Python/hrap_python/test/test_nozzle.py @@ -0,0 +1 @@ +# TODO: Implement nozzle sim tests \ No newline at end of file diff --git a/HRAP - Python/hrap_python/test/test_shift_OF.py b/HRAP - Python/hrap_python/test/test_shift_OF.py new file mode 100644 index 0000000..fae02e8 --- /dev/null +++ b/HRAP - Python/hrap_python/test/test_shift_OF.py @@ -0,0 +1 @@ +# TODO: Implement shifting-OF sim tests \ No newline at end of file diff --git a/HRAP - Python/hrap_python/test/test_tank.py b/HRAP - Python/hrap_python/test/test_tank.py new file mode 100644 index 0000000..179f39c --- /dev/null +++ b/HRAP - Python/hrap_python/test/test_tank.py @@ -0,0 +1,77 @@ +from api.models import * +from api.sim import tank, shift_OF +import unittest + +class TestTankSim(unittest.TestCase): + @classmethod + def setUpClass(cls): + cls.material = MaterialData('propellants/Paraffin.npz') + cls.prop_config = PropellantConfig(ID=LengthValue(2.0, LengthUnit.INCHES), + OD=LengthValue(4.0, LengthUnit.INCHES), + length=LengthValue(12.0, LengthUnit.INCHES), + cstar_eff=1.0, + material=cls.material, + regression_model=shift_OF) + + cls.nozzle_config = NozzleConfig(Cd=1.0, + throat=LengthValue(1.2, LengthUnit.INCHES), + exp_ratio=4.0, + efficiency=1.0) + + def test_large_tank(self): + hardware_config = HardwareConfig(tank_volume=VolumeValue(7000.0, VolumeUnit.CU_CENTIMETERS), + chamber_volume=None, + injector_Cd=0.6, + injector_D=LengthValue(0.25, LengthUnit.INCHES), + injector_N=1, + vent_state=VentConfig.EXTERNAL, + vent_Cd=0.6, + vent_D=LengthValue(0.028, LengthUnit.INCHES)) + + sim_config = SimulationConfig(timestep=0.001, + max_run_time=10.0, + max_burn_time=10.0, + ambient_pressure=PressureValue(1.0, PressureUnit.ATM), + hardware=hardware_config, + prop=self.prop_config, + nozzle=self.nozzle_config) + + init_conditions = InitialConditions(chamber_pressure=PressureValue(1.0, PressureUnit.ATM), + mass_ox=MassValue(5.0, MassUnit.KILOGRAMS), + tank_temp=TemperatureValue(293.15, TemperatureUnit.KELVIN)) + state = SimulationState(sim_config, init_conditions) + tank(sim_config, state) + self.assertAlmostEqual(state.tank.mass_ox, 4.9983216, delta=1e-6) + self.assertAlmostEqual(state.tank.mLiq_new, 4.8706516, delta=1e-6) + self.assertAlmostEqual(state.tank.mLiq_old, 4.8710643, delta=1e-6) + + def test_small_tank(self): + hardware_config = HardwareConfig(tank_volume=VolumeValue(2000.0, VolumeUnit.CU_CENTIMETERS), + chamber_volume=None, + injector_Cd=0.6, + injector_D=LengthValue(0.06125, LengthUnit.INCHES), + injector_N=1, + vent_state=VentConfig.EXTERNAL, + vent_Cd=0.6, + vent_D=LengthValue(0.028, LengthUnit.INCHES)) + + sim_config = SimulationConfig(timestep=0.001, + max_run_time=10.0, + max_burn_time=10.0, + ambient_pressure=PressureValue(1.0, PressureUnit.ATM), + hardware=hardware_config, + prop=self.prop_config, + nozzle=self.nozzle_config) + + init_conditions = InitialConditions(chamber_pressure=PressureValue(1.0, PressureUnit.ATM), + mass_ox=MassValue(1.5, MassUnit.KILOGRAMS), + tank_temp=TemperatureValue(293.15, TemperatureUnit.KELVIN)) + state = SimulationState(sim_config, init_conditions) + tank(sim_config, state) + self.assertAlmostEqual(state.tank.mass_ox, 1.4998993, delta=1e-6) + self.assertAlmostEqual(state.tank.mLiq_new, 1.4814849, delta=1e-6) + self.assertAlmostEqual(state.tank.mLiq_old, 1.4815008, delta=1e-6) + + +if __name__ == '__main__': + unittest.main() \ No newline at end of file diff --git a/HRAP - Python/hrap_python/util/__init__.py b/HRAP - Python/hrap_python/util/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/HRAP - Python/hrap_python/util/nox.py b/HRAP - Python/hrap_python/util/nox.py new file mode 100644 index 0000000..d220f1a --- /dev/null +++ b/HRAP - Python/hrap_python/util/nox.py @@ -0,0 +1,123 @@ +""" +nox.py + +Utilities for deriving nitrous oxide properties at a given temperature +""" +from math import exp +from typing import NamedTuple + +class NOXProperties(NamedTuple): + Pv: float + rho_l: float + rho_v: float + Hv: float + Cp: float + Z: float + +def vapor_pressure(T: float) -> float: + Pc = 7251000.0 + Tc = 309.57 + + #-------------------------------------------------------------------------- + # Vapor Pressure + #-------------------------------------------------------------------------- + # Applicable Range: -90 to 36 Celsius + + a1 = -6.71893 + a2 = 1.35966 + a3 = -1.3779 + a4 = -4.051 + Pv = (Pc*exp((1/(T/Tc))* + (a1*(1-T/Tc) + + a2*((1-(T/Tc)) ** (3/2)) + + a3*((1-(T/Tc)) ** (5/2)) + + a4*((1-(T/Tc)) ** 5)))) + + return Pv + +def get_NOX_properties(T: float) -> NOXProperties: + if T > 309.15 or T < 183.15: + raise ValueError(f'Temperature {T:.2f} is outside the acceptable range for nitrous oxide properties calculation!') + + Tc = 309.57 + rhoc = 452 + R = 188.91 + + Pv = vapor_pressure(T) + + #-------------------------------------------------------------------------- + # Saturated Nitrous Oxide Density + #-------------------------------------------------------------------------- + # Applicable Range: -90 to 36 Celsius + + # Liquid Density + b1 = 1.72328 + b2 = -0.83950 + b3 = 0.51060 + b4 = -0.10412 + rho_l = (rhoc*exp(b1*((1-(T/Tc)) ** (1/3)) + + b2*((1-(T/Tc)) ** (2/3)) + + b3*(1-(T/Tc)) + + b4*((1-(T/Tc)) ** (4/3)))) + + # Vapor Density + c1 = -1.00900 + c2 = -6.28792 + c3 = 7.50332 + c4 = -7.90463 + c5 = 0.629427 + rho_v = (rhoc*exp(c1*(((Tc/T)-1) ** (1/3)) + + c2*(((Tc/T)-1) ** (2/3)) + + c3*((Tc/T)-1) + + c4*(((Tc/T)-1) ** (4/3)) + + c5*(((Tc/T)-1) ** (5/3)))) + + #-------------------------------------------------------------------------- + # Specific Enthalpy + #-------------------------------------------------------------------------- + # Applicable Range: -90 to 35 Celsius + + # Liquid Enthalpy + d1 = -200 + d2 = 116.043 + d3 = -917.225 + d4 = 794.779 + d5 = -589.587 + + # Vapor Enthalpy + e1 = -200 + e2 = 440.055 + e3 = -459.701 + e4 = 434.081 + e5 = -485.338 + + # Latent Heat of Vaporization + Hv = ((e1-d1) + + (e2-d2)*((1-(T/Tc)) ** (1/3)) + + (e3-d3)*((1-(T/Tc)) ** (2/3)) + + (e4-d4)*(1-(T/Tc)) + + (e5-d5)*((1-(T/Tc)) ** (4/3))) + + #-------------------------------------------------------------------------- + # Specific Heat Capacity of Saturated Liquid + #-------------------------------------------------------------------------- + + f1 = 2.49973 + f2 = 0.023454 + f3 = -3.80136 + f4 = 13.0945 + f5 = -14.5180 + + # Specific Heat Capacity + Cp = (f1*(1 + + f2*((1-(T/Tc)) ** -1) + + f3*(1-(T/Tc)) + + f4*((1-(T/Tc)) ** 2) + + f5*((1-(T/Tc)) ** 3))) + + #-------------------------------------------------------------------------- + # Saturated Vapor Compressibility Factor (Tabulated from empirical data) + #-------------------------------------------------------------------------- + Z = Pv / (rho_v*R*T) + + return NOXProperties(Pv=Pv, rho_l=rho_l, rho_v=rho_v, Hv=Hv, Cp=Cp, Z=Z) \ No newline at end of file diff --git a/HRAP - Python/hrap_python/util/units.py b/HRAP - Python/hrap_python/util/units.py new file mode 100644 index 0000000..1913c5c --- /dev/null +++ b/HRAP - Python/hrap_python/util/units.py @@ -0,0 +1,199 @@ +""" +units.py + +Utilities for converting between common units +""" +from enum import Enum +from math import pi + +def d_to_a(d: float) -> float: + return 0.25 * pi * (d ** 2) + +# Converts total impulse to motor class and some percentage +def impulse_to_class_percent(impulse: float) -> tuple[str, float]: + if impulse <= 1.25: + return "A", 100.0 * impulse / 1.25 + elif impulse <= 5: + return "B", 100.0 * (impulse - 1.25) / 2.5 + elif impulse <= 10: + return "C", 100.0 * (impulse - 5) / 5 + elif impulse <= 20: + return "D", 100.0 * (impulse - 10) / 10 + elif impulse <= 40: + return "E", 100.0 * (impulse - 20) / 20 + elif impulse <= 80: + return "F", 100.0 * (impulse - 40) / 40 + elif impulse <= 160: + return "G", 100.0 * (impulse - 80) / 80 + elif impulse <= 320: + return "H", 100.0 * (impulse - 160) / 160 + elif impulse <= 640: + return "I", 100.0 * (impulse - 320) / 320 + elif impulse <= 1280: + return "J", 100.0 * (impulse - 640) / 640 + elif impulse <= 2560: + return "K", 100.0 * (impulse - 1280) / 1280 + elif impulse <= 5120: + return "L", 100.0 * (impulse - 2560) / 2560 + elif impulse <= 10240: + return "M", 100.0 * (impulse - 5120) / 5120 + elif impulse <= 20480: + return "N", 100.0 * (impulse - 10240) / 10240 + elif impulse <= 40960: + return "O", 100.0 * (impulse - 20480) / 20480 + elif impulse <= 81920: + return "P", 100.0 * (impulse - 40960) / 40960 + elif impulse <= 163840: + return "Q", 100.0 * (impulse - 81920) / 81920 + elif impulse <= 327680: + return "R", 100.0 * (impulse - 163840) / 163840 + elif impulse <= 655360: + return "S", 100.0 * (impulse - 327680) / 327680 + elif impulse <= 1310720: + return "T", 100.0 * (impulse - 655360) / 655360 + else: + return None, None + +class LengthUnit(Enum): + METERS = 1 + CENTIMETERS = 2 + MILLIMETERS = 3 + FEET = 4 + INCHES = 5 + +class LengthValue: + CONVERSIONS = { + LengthUnit.METERS: 1.0, + LengthUnit.CENTIMETERS: 0.01, + LengthUnit.MILLIMETERS: 1e-3, + LengthUnit.FEET: 0.3048, + LengthUnit.INCHES: 0.0254 + } + + def __init__(self, value: float, unit: LengthUnit) -> None: + self.base_value = value * LengthValue.CONVERSIONS[unit] + + def get_as(self, unit: LengthUnit) -> float: + return self.base_value / LengthValue.CONVERSIONS[unit] + +class VolumeUnit(Enum): + CU_METERS = 1 + CU_CENTIMETERS = 2 + LITERS = 3 + CU_FEET = 4 + CU_INCHES = 5 + GALLONS = 6 + +class VolumeValue: + CONVERSIONS = { + VolumeUnit.CU_METERS: 1.0, + VolumeUnit.CU_CENTIMETERS: 1e-6, + VolumeUnit.LITERS: 1e-3, + VolumeUnit.CU_FEET: 0.3048 ** 3, + VolumeUnit.CU_INCHES: 0.0254 ** 3, + VolumeUnit.GALLONS: 3.78541e-3 + } + + def __init__(self, value: float, unit: VolumeUnit) -> None: + self.base_value = value * VolumeValue.CONVERSIONS[unit] + + def get_as(self, unit: VolumeUnit) -> float: + return self.base_value / VolumeValue.CONVERSIONS[unit] + + +class PressureUnit(Enum): + PA = 1 + KPA = 2 + MPA = 3 + ATM = 4 + BAR = 5 + PSI = 6 + PSF = 7 + +class PressureValue: + CONVERSIONS = { + PressureUnit.PA: 1.0, + PressureUnit.KPA: 1e3, + PressureUnit.MPA: 1e6, + PressureUnit.ATM: 101325, + PressureUnit.BAR: 1e5, + PressureUnit.PSI: 101325/14.696, + PressureUnit.PSF: 101325/14.686*144 + } + + def __init__(self, value: float, unit: PressureUnit) -> None: + self.base_value = value * PressureValue.CONVERSIONS[unit] + + def get_as(self, unit: PressureUnit) -> float: + return self.base_value / PressureValue.CONVERSIONS[unit] + +class DensityUnit(Enum): + KG_CU_METER = 1 + GRAM_CU_CENTIMETER = 2 + POUND_CU_INCH = 3 + POUND_CU_FOOT = 4 + +class DensityValue: + CONVERSIONS = { + DensityUnit.KG_CU_METER: 1.0, + DensityUnit.GRAM_CU_CENTIMETER: 1e3, + DensityUnit.POUND_CU_INCH: 1.0 / (2.205 * (0.0254**3)), + DensityUnit.POUND_CU_FOOT: 1.0 / (2.205 * (0.3048**3)) + } + + def __init__(self, value: float, unit: DensityUnit) -> None: + self.base_value = value * DensityValue.CONVERSIONS[unit] + + def get_as(self, unit: DensityUnit) -> float: + return self.base_value / DensityValue.CONVERSIONS[unit] + +class MassUnit(Enum): + KILOGRAMS = 1 + GRAMS = 2 + POUNDS = 3 + OUNCES = 4 + NEWTONS = 5 # Included for simplicity - no need to separate mass and force + +class MassValue: + CONVERSIONS = { + MassUnit.KILOGRAMS: 1.0, + MassUnit.GRAMS: 1e-3, + MassUnit.POUNDS: 0.453592, + MassUnit.OUNCES: 0.0283495, + MassUnit.NEWTONS: 0.10197 + } + + def __init__(self, value: float, unit: MassUnit) -> None: + self.base_value = value * MassValue.CONVERSIONS[unit] + + def get_as(self, unit: MassUnit) -> float: + return self.base_value / MassValue.CONVERSIONS[unit] + +class TemperatureUnit(Enum): + KELVIN = 1 + CELSIUS = 2 + RANKINE = 3 + FAHRENHEIT = 4 + +class TemperatureValue: + def __init__(self, value: float, unit: TemperatureUnit) -> None: + # These conversions are done manually due to the addition/subtraction in Fahrenheit conversions + if unit == TemperatureUnit.CELSIUS: + self.base_value = value + 273.15 + elif unit == TemperatureUnit.RANKINE: + self.base_value = value / 1.8 + elif unit == TemperatureUnit.FAHRENHEIT: + self.base_value = ((value - 32) / 1.8) + 273.15 + else: + self.base_value = value + + def get_as(self, unit: TemperatureUnit) -> float: + if unit == TemperatureUnit.CELSIUS: + return self.base_value - 273.15 + elif unit == TemperatureUnit.RANKINE: + return self.base_value * 1.8 + elif unit == TemperatureUnit.FAHRENHEIT: + return ((self.base_value - 273.15) * 1.8) + 32 + else: + return self.base_value + \ No newline at end of file diff --git a/HRAP - Python/python/components/injector.py b/HRAP - Python/python/components/injector.py deleted file mode 100644 index 5f3de29..0000000 --- a/HRAP - Python/python/components/injector.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Injector! -""" - - -class Injector: - def __init__(self, diameter, c_discharge, num_injectors, vent_state, vent_diameter=None, vent_c_discharge=None): - self.diameter = diameter # Default cm - self.c_discharge = c_discharge - self.num_injectors = num_injectors - self.vent_state = vent_state - self.vent_diameter = vent_diameter # Default cm - self.vent_c_discharge = vent_c_discharge - diff --git a/HRAP - Python/python/components/mass.py b/HRAP - Python/python/components/mass.py deleted file mode 100644 index fec2f1a..0000000 --- a/HRAP - Python/python/components/mass.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Calculating the mass properties (optional field) -""" - - -class MassProperties: - - def __init__(self, ox_tank_location, fuel_grain_location, empty_motor_com, empty_motor_mass): - self.ox_tank_loc = ox_tank_location # Default cm, measured from fore - self.fuel_grain_loc = fuel_grain_location # Default cm, measured from fore - self.empty_motor_com = empty_motor_com # Default cm, measured from fore - self.empty_motor_mass = empty_motor_mass # Default kg diff --git a/HRAP - Python/python/components/nozzle.py b/HRAP - Python/python/components/nozzle.py deleted file mode 100644 index aaac1cb..0000000 --- a/HRAP - Python/python/components/nozzle.py +++ /dev/null @@ -1,11 +0,0 @@ -""" -Representation of the nozzle. -""" - - -class Nozzle: - def __init__(self, throat_diam, efficiency, c_discharge): - self.throat_diameter = throat_diam # How to handle different units? Universal units array? - self.efficiency = efficiency # Comes in as a percentage - self.c_discharge = c_discharge - self.expansion_ratio = None # Not sure what this is, also exit diameter diff --git a/HRAP - Python/python/components/ox_tank.py b/HRAP - Python/python/components/ox_tank.py deleted file mode 100644 index 6c46fdd..0000000 --- a/HRAP - Python/python/components/ox_tank.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Equivalent of "Tank Dimensions" box in UI and more. -Thinking of taking an object-oriented approach for this. -""" - - -class OxTank: - def __init__(self, ox_vol): - self.volume = ox_vol # TODO: add somewhere converting dims into volume. idk wherever this gets used - - - diff --git a/HRAP - Python/python/components/propellant.py b/HRAP - Python/python/components/propellant.py deleted file mode 100644 index 11fee73..0000000 --- a/HRAP - Python/python/components/propellant.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Propellant. This can be either loaded in or taken from UI -""" - - -class Propellant: - - def __init__(self, name, density, c_star_eff, grain_id, grain_od, - grain_length, const_of_ratio=None, c_regression=None, exp_regression=None, exp_length=None): - self.name = name - self.density = density # Default kg/m^3 - self.const_of_ratio = const_of_ratio # Constant OF only - self.c_regression = c_regression # mm/s, Regression only - self.exp_regression = exp_regression # kg/m^2-s, Regression only - self.exp_length = exp_length # m, Regression only - self.c_star_eff = c_star_eff # C* efficiency, percent - self.grain_id = grain_id # Default cm - self.grain_od = grain_od # Default cm - self.grain_length = grain_length # Default cm - - -def load_from_csv(filepath): - # You can also load from CSV! headers should be: - # name,density,const_of,const_of_ratio,c_star_eff,grain_id,grain_od,grain_length - return Propellant() diff --git a/HRAP - Python/python/components/sim_config.py b/HRAP - Python/python/components/sim_config.py deleted file mode 100644 index 8897776..0000000 --- a/HRAP - Python/python/components/sim_config.py +++ /dev/null @@ -1,18 +0,0 @@ -""" -Simulation configuration (contains all other models) -""" - - -class SimulationConfiguration: - def __init__(self, tank_temp, chamber_pressure, oxidizer_mass, ambient_press, - max_sim_runtime, max_burn_time, sim_timestep, regression_model): - self.tank_temp = tank_temp # Default K. TODO: account for ppl inputting pressure instead - self.chamber_pressure = chamber_pressure # Default atm - self.starting_ox_mass = oxidizer_mass # Default kg. TODO: account for tank fill percentage input - self.ambient_pressure = ambient_press # Default atm - - self.max_sim_runtime = max_sim_runtime # seconds - self.max_burn_time = max_burn_time # seconds - self.sim_timestep = sim_timestep # seconds - self.regression_model = regression_model - diff --git a/HRAP - Python/python/util/helpers.py b/HRAP - Python/python/util/helpers.py deleted file mode 100644 index aaeeb1a..0000000 --- a/HRAP - Python/python/util/helpers.py +++ /dev/null @@ -1,53 +0,0 @@ -""" -helpers.py - -Extra helper function for conversions and units that are not available -in other packages -""" - - -# Converts total impulse to motor class and some percentage -# MATLAB equivalent; impulse.m -def impulse_to_class_percent(impulse): - if impulse <= 1.25: - return "A", 100 * impulse / 1.25 - elif impulse <= 5: - return "B", 100 * (impulse - 1.25) / 2.5 - elif impulse <= 10: - return "C", 100 * (impulse - 5) / 5 - elif impulse <= 20: - return "D", 100 * (impulse - 10) / 10 - elif impulse <= 40: - return "E", 100 * (impulse - 20) / 20 - elif impulse <= 80: - return "F", 100 * (impulse - 40) / 40 - elif impulse <= 160: - return "G", 100 * (impulse - 80) / 80 - elif impulse <= 320: - return "H", 100 * (impulse - 160) / 160 - elif impulse <= 640: - return "I", 100 * (impulse - 320) / 320 - elif impulse <= 1280: - return "J", 100 * (impulse - 640) / 640 - elif impulse <= 2560: - return "K", 100 * (impulse - 1280) / 1280 - elif impulse <= 5120: - return "L", 100 * (impulse - 2560) / 2560 - elif impulse <= 10240: - return "M", 100 * (impulse - 5120) / 5120 - elif impulse <= 20480: - return "N", 100 * (impulse - 10240) / 10240 - elif impulse <= 40960: - return "O", 100 * (impulse - 20480) / 20480 - elif impulse <= 81920: - return "P", 100 * (impulse - 40960) / 40960 - elif impulse <= 163840: - return "Q", 100 * (impulse - 81920) / 81920 - elif impulse <= 327680: - return "R", 100 * (impulse - 163840) / 163840 - elif impulse <= 655360: - return "S", 100 * (impulse - 327680) / 327680 - elif impulse <= 1310720: - return "T", 100 * (impulse - 655360) / 655360 - else: - return None, None