Exemplo n.º 1
0
def plot_condensation_curve(AR_min, AR_max, p_c, fp, T_ref):
    gamma = fp.get_specific_heat_ratio(T=T_ref,
                                       p=p_c)  # [-] Specific heat ratio

    # Determine maximum Mach number at exit
    M_exit_max = IRT.Mach_from_area_ratio(
        AR=AR_max, gamma=gamma)  # [-] Max. Mach number at exit
    M_exit_min = IRT.Mach_from_area_ratio(
        AR=AR_min, gamma=gamma)  # [-] Min. Mach number at exit
    M_exit = np.linspace(start=M_exit_min, stop=M_exit_max,
                         num=50)  # [-] Range of exit Mach numbers to evalulate

    # Determine pressure at exit
    PR_exit = IRT.pressure_ratio(M=M_exit,
                                 gamma=gamma)  # [-] Pressure ratio at exit
    p_exit = p_c / PR_exit  # [-] Exit pressure

    # Determine saturation temperature at exit
    T_sat_exit = fp.get_saturation_temperature(
        p=p_exit)  # [K] Saturation temperature at exit
    # print(p_exit)
    # print(T_sat_exit)
    TR_exit = IRT.temperature_ratio(
        M=M_exit, gamma=gamma)  # [-] Temperature ratio at exit
    T_chamber = T_sat_exit * TR_exit  # [K] Chamber temperature that would result precisely in saturation temperature at nozzle exit

    AR = IRT.area_ratio(
        M=M_exit, gamma=gamma
    )  # [-] Area ratios corresponding to all specified mach numbers

    plt.plot(
        AR,
        T_chamber,
        label="$p_c ={:2.0f}$ bar , $T_c={:3.0f}$ K, $\\gamma={:1.3f}$".format(
            p_c * 1e-5, T_ref, gamma))
Exemplo n.º 2
0
    def test_TRP_example(self):
        # Slide 57-51 from Ideal Rocket Motor lecture from Thermal Rocket Propulsion course are used as example (Zandbergen)
        F_expected = 401.8e3
        u_exit_expected = 3021
        m_dot_expected = 132.9
        gamma = 1.3
        R = 390.4
        p_chamber = 5.6e6
        p_back = 6.03e3
        T_chamber = 3400
        AR_exit = 49
        d_exit = 1.6
        A_exit = 0.25 * math.pi * d_exit**2
        A_throat = A_exit / AR_exit

        NS = "isentropically expanded (margin: 0.01)"
        res = IRT.get_engine_performance(p_chamber=p_chamber,
                                         T_chamber=T_chamber,
                                         A_throat=A_throat,
                                         AR_exit=AR_exit,
                                         p_back=p_back,
                                         gamma=gamma,
                                         R=R)
        self.assertAlmostEqual(res['thrust'], F_expected, -3)
        self.assertAlmostEqual(res['m_dot'], m_dot_expected, 0)
        self.assertAlmostEqual(res['u_exit'], u_exit_expected, -1)
        self.assertEqual(res['nozzle_status'], NS)
Exemplo n.º 3
0
    def test_simple_input(self):
        # Expected exit velocity and mass flow
        # Should be sonic throat too, due to vacuum at exit
        u_exit_expected = 1.0801234497346432
        m_dot_expected = 0.6847314563772704  # Should be equal to Gamma(1.4) (vandankerkhove function)
        p_exit = 0.5282817877171743  # Should be exit pressure for M=1
        AR_exit = 1
        T_chamber = 1
        R = 1
        gamma = 1.4
        p_chamber = 1
        A_throat = 1
        AR_exit = 1
        p_back = 0

        # Expected thrust
        F_expected = m_dot_expected * u_exit_expected + p_exit * A_throat * AR_exit
        res = IRT.thrust(p_chamber=p_chamber,
                         T_chamber=T_chamber,
                         A_throat=A_throat,
                         AR_exit=AR_exit,
                         p_back=p_back,
                         gamma=gamma,
                         R=R)
        self.assertEqual(res, F_expected)
Exemplo n.º 4
0
    def test_simple_input(self):
        u_exit_expected = 1.0801234497346432
        m_dot_expected = 0.6847314563772704  # Should be equal to Gamma(1.4) (vandankerkhove function)
        p_exit = 0.5282817877171743  # Should be exit pressure for M=1
        AR_exit = 1
        T_chamber = 1
        R = 1
        gamma = 1.4
        p_chamber = 1
        A_throat = 1
        AR_exit = 1
        p_back = 0

        # Expected thrust
        F_expected = m_dot_expected * u_exit_expected + p_exit * A_throat * AR_exit
        # Since the throat is M=1 and the back pressure is vacuum, it is clearly underexpanded
        NS = "underexpanded"
        res = IRT.get_engine_performance(p_chamber=p_chamber,
                                         T_chamber=T_chamber,
                                         A_throat=A_throat,
                                         AR_exit=AR_exit,
                                         p_back=p_back,
                                         gamma=gamma,
                                         R=R)
        self.assertEqual(res['thrust'], F_expected)
        self.assertEqual(res['m_dot'], m_dot_expected)
        self.assertEqual(res['u_exit'], u_exit_expected)
        self.assertEqual(res['nozzle_status'], NS)
Exemplo n.º 5
0
    def test_table_from_TRP(self):
        # Page 51 from the TRP reader has a table with in and outputs. It is used to verify the function here
        in_out = ((1.05, 0.6177), (1.10, 0.6284), (1.12, 0.6325),
                  (1.19, 0.6466), (1.23, 0.6543), (1.31, 0.6691),
                  (1.38, 0.6813), (1.65, 0.7238))

        for input, output in in_out:
            res = IRT.vdk(input)
            self.assertEqual(round(res, 4), output)
Exemplo n.º 6
0
 def test_Anderson_example(self):
     # Example 10.5 from Anderson2016 (recalculated manually due to different formula, calculation approach)
     expected_result = 586.1  # [kg/s]
     res = IRT.mass_flow(p_chamber=3.03e6,
                         A_throat=0.4,
                         R=520,
                         T_chamber=3500,
                         gamma=1.22)
     self.assertEqual(round(res, 1), expected_result)
Exemplo n.º 7
0
    def testAndersonTable(self):
        # Test values from Appendix B from Anderson
        gamma = 1.4

        in_out = ((0.102e1, 0.1047e1, 3), (0.158e1, 0.2746e1, 3),
                  (0.345e1, 0.1372e2, 2), (0.61e1, 0.4324e2, 2), (0.36e2,
                                                                  0.1512e4, 0))

        for M, PR, places in in_out:
            res = IRT.pressure_ratio_shockwave(M=M, gamma=gamma)
            self.assertAlmostEqual(res, PR, places)
Exemplo n.º 8
0
 def test_TRP_example(self):
     # Slide 57-51 from Ideal Rocket Motor lecture from Thermal Rocket Propulsion course are used as example (Zandbergen)
     expected = 3021
     gamma = 1.3
     R = 390.4
     T_chamber = 3400
     AR_exit = 49
     res = IRT.exit_velocity(AR_exit=AR_exit,
                             T_chamber=T_chamber,
                             R=R,
                             gamma=gamma)
     self.assertAlmostEqual(
         res, expected, -1)  # 4 digits accuracy (-1, rounding difference)
Exemplo n.º 9
0
    def testAndersonTable(self):  # Appendix A and B from Anderson2016
        # For this case the pressure ratios of two tables must be multiplied. Accuracy unknown after multiplication
        # Lets guess 1 digit loss is fine
        gamma = 1.4

        in_out = (
            (0.1760e1, 0.8458e1, 0.4736e1, 3),  # M = 2.05
            (0.1395e2, 0.2247e3, 0.2140e2, 2)  # M = 4.3
        )

        for AR, PR_exit, PR_shockwave, places in in_out:
            res = IRT.exit_pressure_ratio_shockwave(exit_area_ratio=AR,
                                                    gamma=gamma)
            self.assertAlmostEqual(res, PR_exit / PR_shockwave, places)
Exemplo n.º 10
0
def plot_pressure_curve(AR_min, AR_max, p_c, fp, T_ref):
    gamma = fp.get_specific_heat_ratio(T=T_ref,
                                       p=p_c)  # [-] Specific heat ratio

    # Determine maximum Mach number at exit
    M_exit_max = IRT.Mach_from_area_ratio(
        AR=AR_max, gamma=gamma)  # [-] Max. Mach number at exit
    M_exit_min = IRT.Mach_from_area_ratio(
        AR=AR_min, gamma=gamma)  # [-] Min. Mach number at exit
    M_exit = np.linspace(start=M_exit_min, stop=M_exit_max,
                         num=50)  # [-] Range of exit Mach numbers to evalulate

    # Determine pressure at exit
    PR_exit = IRT.pressure_ratio(M=M_exit,
                                 gamma=gamma)  # [-] Pressure ratio at exit
    p_exit = p_c / PR_exit  # [-] Exit pressure

    AR = IRT.area_ratio(
        M=M_exit, gamma=gamma
    )  # [-] Area ratios corresponding to all specified mach numbers

    plt.plot(AR,
             p_exit * 1e-5,
             label="{:2.0f} bar , $\\gamma={:1.2f}$".format(p_c * 1e-5, gamma))
Exemplo n.º 11
0
 def testAndersonTable(self):
     # use Anderson2016 table Appendix for gamma-1.4 in order to verify function workings
     in_out = (
         (0.2894e2, 0.2e-1, False, 5),
         (0.2708e1, 0.22e0, False, 4),
         (0.1213e1, 0.58e0, False, 4),
         #(0.1000e1, 0.98e0, False, 2), # Function is extremely insensitive to results close to M=1
         #(0.1000e1, 0.102e1, True, 3),
         (0.1126e1, 0.1420e1, True, 3),
         (0.2763e1, 0.2550e1, True, 3),
         (0.3272e3, 0.9e1, True, 3),
         (0.1538e5, 0.2e2, True, 2))
     for AR, expected_M, supersonic, M_places in in_out:
         res_M = IRT.Mach_from_area_ratio(AR=AR,
                                          gamma=1.4,
                                          supersonic=supersonic)
         self.assertAlmostEqual(res_M, expected_M, places=M_places)
Exemplo n.º 12
0
 def testAndersonTable(self):
     # Using table from Anderon to test for different back pressures
     # Taking line for M=0.5 first for subsonic test
     p_chamber = 1.186e6
     exit_area_ratio = 1.340
     gamma = 1.4
     p_exit = 1e6
     res = IRT.nozzle_status(p_chamber=p_chamber,
                             p_back=p_exit,
                             AR_exit=exit_area_ratio,
                             gamma=gamma)
     self.assertEqual(res, 'subsonic')
     p_chamber = 1.54e6  # Slight below value for which it will not have a shock in the nozzle
     res = IRT.nozzle_status(p_chamber=p_chamber,
                             p_back=p_exit,
                             AR_exit=exit_area_ratio,
                             gamma=gamma)
     self.assertEqual(res, 'shock in nozzle')
     # Now test the same but with slightly higher pressure to see if it returns underexpanded
     p_chamber = 1.55e6  # Slight above value for which it will not have a shock in the nozzle
     res = IRT.nozzle_status(p_chamber=p_chamber,
                             p_back=p_exit,
                             AR_exit=exit_area_ratio,
                             gamma=gamma)
     self.assertEqual(res, 'overexpanded')
     # Now isentropic expansion (tolerance is set 0.01)
     # Area ratio must be updated to a value present in Anderson table for M>1
     # M = 2.1 is selected
     exit_area_ratio = 1.837
     p_chamber = 9.145e6
     res = IRT.nozzle_status(p_chamber=p_chamber,
                             p_back=p_exit,
                             AR_exit=exit_area_ratio,
                             gamma=gamma)
     self.assertEqual(res, 'isentropically expanded (margin: 0.01)')
     # Same but with exit pressure slightly below margin set, so it is overexpanded
     p_exit = 0.9899e6
     res = IRT.nozzle_status(p_chamber=p_chamber,
                             p_back=p_exit,
                             AR_exit=exit_area_ratio,
                             gamma=gamma)
     self.assertEqual(res, 'underexpanded')
     # Same but slightly higher so it is underexpanded
     p_exit = 1.01e6
     res = IRT.nozzle_status(p_chamber=p_chamber,
                             p_back=p_exit,
                             AR_exit=exit_area_ratio,
                             gamma=gamma)
     self.assertEqual(res, 'overexpanded')
Exemplo n.º 13
0
 def test_TRP_example(self):
     # Slide 57-51 from Ideal Rocket Motor lecture from Thermal Rocket Propulsion course are used as example (Zandbergen)
     expected = 401.8e3
     gamma = 1.3
     R = 390.4
     p_chamber = 5.6e6
     p_back = 6.03e3
     T_chamber = 3400
     AR_exit = 49
     d_exit = 1.6
     A_exit = 0.25 * math.pi * d_exit**2
     A_throat = A_exit / AR_exit
     res = IRT.thrust(p_chamber=p_chamber,
                      T_chamber=T_chamber,
                      A_throat=A_throat,
                      AR_exit=AR_exit,
                      p_back=p_back,
                      gamma=gamma,
                      R=R)
     self.assertAlmostEqual(
         expected, res,
         -3)  # Slight rounding error again and 1 digit accuracy less
Exemplo n.º 14
0
 def testOne(self):
     # Mach is 1 input should return 1
     expected = 1
     res = IRT.pressure_ratio_shockwave(M=1, gamma=1.4)
     self.assertEqual(res, expected)
Exemplo n.º 15
0
fp = FluidProperties(td['propellant']) # Object to access fluid properties with
p_c = 5e5#td['p_inlet'] # [bar] Chamber pressure
T_c = 600#td['T_chamber_guess'] # [K] Chamber temperature
h_channel = td['h_channel'] # [m] Channel/nozzle depth
w_throat = td['w_throat'] # [m] Throat width
AR_exit = 10 #td['AR_exit'] # [-] Exit area ratio
p_back = 0# td['p_back'] # [Pa] Atmospheric pressire

print("Chamber temperature: {:3.2f} K".format(T_c))

# Calculate throat area, and propellant properties
A_throat = h_channel*w_throat # [m^2] Thrpat area
gamma = fp.get_specific_heat_ratio(T=T_c, p=p_c) # [-] Get gamma at specified gas constant
R = fp.get_specific_gas_constant() # [J/(kg*K)] Specific gas constant

ep = IRT.get_engine_performance(p_chamber=p_c, T_chamber=T_c, A_throat=A_throat, AR_exit=AR_exit, p_back=p_back, gamma=gamma, R=R)
print("\n --- IRT predictions --- ")
print("Isp: {:3.2f} s".format(ep['Isp']))
print("Gamma: {:1.3f} ".format(gamma))
print("Mass flow: {:2.3f} mg/s".format(ep['m_dot']*1e6))
print("Thrust: {:2.3f} mN".format(ep['thrust']*1e3))

zeta_CF = td['F']/ep['thrust']
Isp_real = td['F']/td['m_dot']/g0
zeta_Isp = Isp_real/ep['Isp']
discharge_factor = td['m_dot']/ep['m_dot']

print('\n --- Experimental values ---')
print("Isp: {:3.2f} s (zeta_Isp = {:1.3f})".format(Isp_real, zeta_Isp))
print("Mass flow: {:2.3f} mg/s (Cd = {:1.3f})".format(td['m_dot']*1e6, discharge_factor))
print("Thrust: {:2.3f} mN (zeta_CF ={:1.3f})".format(td['F'], zeta_CF))
Exemplo n.º 16
0
def run(F_desired, T_chamber, channel_amount, settings, x_guess):
    """ Algorithm to determine the thruster with the best specific impulse for the given thrust and power requirements

    Args:
        F_desired (N): Required thrust
        T_chamber (K): Chamber temperature determines mass flow and Isp and ideal power consumption
        channel_amount (-): Amount of channels. Technically, this must be optmized, but using built-in optimization really doesn't work well for integers
        In settings {dict}:
            FDP (dict): Fixed design parameters
            bounds (dict of 2-tuples): The constraints on the design parameters to be optimized
            NR (dict of functions): Nusselt relations that are used in various phases in the channel (liquid, two-phase, dry-out,gas)
            PDR (dict of functions): Pressure drop relations that are used in various phases in the channel (friction: liquid, two-phase, gas; contraction)
            steps (dict of ints): Fidelity of the temperature-steps in the various sections of the channel
    """
    print("\n  ----- OPTIMIZING THRUSTER FOR POWER CONSUMPTION -----")
    print("\n    HIGH-LEVEL INPUTS")
    print("--- Desired thrust:        {:3.1f} mN".format(F_desired * 1e3))
    print("--- Chamber temperature:   {:4.1f} K".format(T_chamber))

    ## First determine ideal engine performance, and load settings for that
    T_inlet = settings['FDP'][
        'T_inlet']  # [K] Inlet temperature (at heating channel inlet manifold)
    p_inlet = settings['FDP'][
        'p_inlet']  # [Pa] Chamber pressure assumed equal to inlet pressure
    AR_exit = settings['FDP']['AR_exit']  # [-] Exit area ratio of nozzle
    p_back = settings['FDP']['p_back']  # [Pa]
    assert p_back == 0  # Nozzle correction does not work for back pressure
    fp = settings['FDP'][
        'fp']  # [object] FluidProperties object containing thermodynamic parameters for propellant

    print("--- Inlet pressure:          {:2.1f} bar".format(p_inlet * 1e-5))
    print("--- Exit area ratio          {:2.1f}".format(AR_exit))

    # Check if the inputs are correct
    assert (T_chamber > T_inlet)
    ep_ideal = IRT.engine_performance_from_F_and_T(F_desired=F_desired,
                                                   p_chamber=p_inlet,
                                                   T_chamber=T_chamber,
                                                   AR_exit=AR_exit,
                                                   p_back=p_back,
                                                   fp=fp)

    # Calculate ideal power consumption
    m_dot = ep_ideal['m_dot']  # [kg/s] Mass flow through chamber
    Isp = ep_ideal['Isp']  # [s] Specific impulse
    P_ideal = chamber.ideal_power_consumption(  # [W] Ideal power consumption
        mass_flow=m_dot,
        T_inlet=T_inlet,
        p_inlet=p_inlet,
        T_outlet=T_chamber,
        p_outlet=p_inlet,
        fp=fp)

    print("\n    IDEAL PERFORMANCE")
    print("--- Nozzle status:           {}".format(ep_ideal['nozzle_status']))
    print("--- Mass flow:               {:3.2f} mg/s".format(m_dot * 1e6))
    print("--- Isp:                     {:3.1f} s".format(Isp))
    print("--- Power consumption:       {:2.1f} W".format(P_ideal))

    # Calculate pre-calculated values of heating channel that are constant through entire optimization
    prepared_values = oneD.full_homogenous_preparation(
        T_inlet=T_inlet,  # [K] Inlet temperature of the channel
        T_outlet=
        T_chamber,  # [K] Outlet of the channel is equal to chamber temperature in IRT
        m_dot=m_dot / channel_amount,  # [kg/s] Mass flow in a single channel
        p_ref=
        p_inlet,  # [Pa] The pressure in the channel is assumed to be constant for heat flow calculation purposes
        steps_l=settings['steps']
        ['steps_l'],  # [-] Fidelity of simulation in liquid section of channel
        steps_tp=settings['steps']
        ['steps_tp'],  # [-] Fidelity of simulation in two-phase section of channel
        steps_g=settings['steps']
        ['steps_g'],  # [-] Fidelity of simulation in gas section of channel
        fp=
        fp,  # [object] Object to access thermodynamic parameters of propellant with
    )

    ### Define function to optimize here, and load settings into fixed design parameters
    ### IMPORTANT NOTE: if the order of arguments of f() is changed, the order of bounds must be changed too.
    # The bounds are defined a bit early here, so one does not forgot this after changing either.
    b = Bounds(
        [  # Lower bounds
            settings['bounds']['w_channel'][0],
            settings['bounds']['w_channel_spacing'][0],
            settings['bounds']['T_wall_superheat'][0] + T_chamber,
        ],
        [  # Higher bounds
            settings['bounds']['w_channel'][1],
            settings['bounds']['w_channel_spacing'][1],
            settings['bounds']['T_wall_superheat'][1] + T_chamber,
        ])
    # Initial guess (just make it the higher bound (arbitary)) NOTE: SAME ORDER APPLIES ABOUNDS AND AS f() below
    x0 = None  # Set the first guess based on a guess being provided or not
    if (x_guess is None):
        x0 = [
            settings['bounds']['w_channel'][0],
            settings['bounds']['w_channel_spacing'][0],
            settings['bounds']['T_wall_superheat'][0] + T_chamber,
        ]
    else:
        x0 = x_guess

    # The function to optimize
    def f(x,
          return_full_results=False
          ):  #  NOTE: change bounds above if argument change
        if (return_full_results):
            print("Going to return full results.")
        w_channel = x[0]
        w_channel_spacing = x[1]
        T_wall = x[2]
        # Wall arguments need to be encapsulated in a dictionary so they can be conveniently passed to the section of code calcuating wall effects
        # and the actual bottom wall temperature
        wall_args = {
            'kappa_wall': settings['FDP']
            ['kappa_wall'],  # [W/(m*K)] Thermal conductivity of chip wall
            'h_channel':
            settings['FDP']['h_channel'],  # [m] Channel depth, channel height
            'w_channel': w_channel,  # [m] Channel width
            'w_channel_spacing':
            w_channel_spacing,  # [m] Spacing between channels/wall thickness
            'emissivity_chip_bottom': settings['FDP']
            ['emissivity_chip_bottom'],  # [m] Emissivity of the bottom of the chip
        }
        # Exit manifold length is zero based on existing VLM design, so should be zero in settings
        assert settings['FDP'][
            'l_exit_manifold'] == 0  # It is merely a remnant from previous code choices
        # The function that returns the eventual power output
        res_P = optim_P_total(  # CAPITALIZED COMMENTS ARE FOR OPTIMIZED VARIABLES
            channel_amount=channel_amount,  # [-] AMOUNT OF CHANNELS
            w_channel=w_channel,  # [m] CHANNEL WIDTH
            h_channel=settings['FDP']
            ['h_channel'],  # [m] Channel depth, channel height
            inlet_manifold_length_factor=settings['FDP']
            ['inlet_manifold_length_factor'],  # [-] Linear factor to determine inlet manifold length
            inlet_manifold_width_factor=settings['FDP']
            ['inlet_manifold_width_factor'],  # [-] Linear factor to determine inlet manifold width
            l_exit_manifold=settings['FDP']
            ['l_exit_manifold'],  # [-] NOTE: old part of design. Is set to zero.
            w_channel_spacing=
            w_channel_spacing,  # [m] SPACING BETWEEN CHANNELS/WALL THICKNESS
            w_outer_margin=settings['FDP']
            ['w_outer_margin'],  # [m] Margin around edge of heating channels, or nozzle width
            T_wall=T_wall,  # [K] WALL TEMPERATURE
            p_ref=p_inlet,  # [Pa] Pressure through channel
            m_dot=m_dot,  # [kg/s] Heat flow through channel
            prepared_values=
            prepared_values,  # {dict} Dictionary with many parameters that are costly to calcuate but are cosntant throughout optimization
            Nusselt_relations=settings[
                'Nusselt_relations'],  # {dict} Heat transfer relations used for optimization
            pressure_drop_relations=settings[
                'pressure_drop_relations'],  # {dict} Pressure drops used for optimization
            convergent_half_angle=settings['FDP']
            ['convergent_half_angle'],  # [rad] Half-angle of convergent part of 2D-conical nozzle
            divergent_half_angle=settings['FDP']
            ['divergent_half_angle'],  # [rad] Half-angle of divergent part of 2D-conical nozzle
            F_desired=
            F_desired,  # [N] Desired thrust. Still required to correct for pressure drop
            p_back=
            p_back,  # [Pa] Back pressure. NOTE: Nozzle correction depends on this being 0.
            AR_exit=
            AR_exit,  # [-] Exit area ratio to correct nozzle after pressure drop
            emissivity_top=settings['FDP']
            ['emissivity_chip_top'],  # [-] Emissivity of top chip (black-body-radiation)
            fp=
            fp,  # [object] Object to access thermodynamic parameters of propellant with
            wall_args=
            wall_args,  # {dict} Arguments about wall design. See aboves                          
        )
        # Return the total power consumption. The variable of interest
        P_total = P_ideal + res_P['P_loss']
        #print(x)

        if math.isnan(P_total):
            #print("A constraint violated.")
            # Punish the objective function by outputting high power consumptions for invalid solutions
            # The punishment must however not be constant, or it will converge at the boundary where it is invalid.
            # Since the punishment stems from the pressure drop being higher than the initial p_chamber value, the lower the negative final pressure is the higher the punishment
            if return_full_results:
                print("Converged on invalid result!")
                print(res_P['pressure_drop_punishment'])
            return (P_ideal * (2 + 1 * res_P['pressure_drop_punishment'])
                    ) * settings['function_scaling']
        else:
            #print("P_total: {:2.5f} W".format(P_total))
            # Scipy minimize can't handle multiple returns for the objective function, so the extensive final results must be pulled out afterwards
            if return_full_results:
                print("Returning full results!")
                print(res_P['P_loss'])
                return res_P
            else:
                return P_total * settings['function_scaling']

    ## OPTIMIZATION ALGORITHM
    # First bounds must be set in the same order that arguments are given in f()
    # The order is: channel_amount, w_channel,w_channel_spacing,T_wall
    optimization_method = 'L-BFGS-B'
    optimization_options = {
        'ftol': 2.220446049250313e-20,
        'gtol': 1e-10,
        'disp': False,
    }

    # Pressure drop constraint
    con_pressure_drop = NonlinearConstraint(f, 0, 2 * P_ideal)

    minimize_results = minimize(fun=f,
                                x0=x0,
                                bounds=b,
                                method=optimization_method,
                                options=optimization_options)

    #
    print(minimize_results.x)
    print(minimize_results.success)
    print(minimize_results.status)
    print(minimize_results.message)
    print(minimize_results.nit)
    print("Trying to return full results")
    res_final = f(minimize_results.x, return_full_results=True)
    print(res_final['P_loss'])

    optim_results = {
        #'full_res': res_final['res'],
        #'full_prepared_values': res_final['prepared_values'],
        'minimize_results': minimize_results,
        'w_channel': minimize_results.x[0],
        'w_channel_spacing': minimize_results.x[1],
        'T_wall_top': minimize_results.x[2],
        'P_total': minimize_results.fun / settings['function_scaling'],
        'P_loss': res_final['P_loss'],
        'P_ideal': P_ideal,
        'l_channel': res_final['l_channel'],
        'h_channel': res_final['h_channel'],
        'hydraulic_diameter': res_final['hydraulic_diameter'],
        'l_inlet': res_final['l_inlet'],
        'l_total': res_final['l_total'],
        'A_chip': res_final['A_chip'],
        'l_outlet': res_final['l_outlet'],
        'l_convergent': res_final['l_convergent'],
        'l_divergent': res_final['l_divergent'],
        'l_channel_l': res_final['l_channel_l'],
        'l_channel_tp': res_final['l_channel_tp'],
        'l_channel_g': res_final['l_channel_g'],
        'T_wall_bottom': res_final['T_wall_bottom'],
        'w_total': res_final['w_total'],
        'w_channels_total': res_final['w_channels_total'],
        'w_nozzle': res_final['w_nozzle'],
        'w_inlet': res_final['w_inlet'],
        'pressure_drop': res_final['pressure_drop'],
        'Re_channel_l': res_final['Re_channel_l'],
        'Re_channel_tp': res_final['Re_channel_tp'],
        'Re_channel_g': res_final['Re_channel_g'],
        'M_channel_exit_after_dP': res_final['M_channel_exit_after_dP'],
        'Re_channel_exit_after_dP': res_final['Re_channel_exit_after_dP'],
        'm_dot': ep_ideal['m_dot'],
        'Isp': ep_ideal['Isp'],
        'p_inlet': p_inlet,
        'w_throat_new': res_final['w_throat_new'],
        'Re_throat_new': res_final['Re_throat_new'],
    }
    #print(optim_results)
    return optim_results
Exemplo n.º 17
0
 def test_zero_two(self):
     expected_result = 0.769800358919501
     res = IRT.mass_flow(p_chamber=1, A_throat=1, R=1, T_chamber=1, gamma=2)
     self.assertEqual(res, expected_result)
Exemplo n.º 18
0
 def test_simple_input(self):
     expected = 1.0801234497346432  # Manually calculated
     # area ratio of 1 means the mach number is 1
     res = IRT.exit_velocity(AR_exit=1, T_chamber=1, R=1, gamma=1.4)
     self.assertEqual(res, expected)
Exemplo n.º 19
0
 def testAreaRatioOne(self):
     expected_result = 1.0
     input = 1
     res = IRT.Mach_from_area_ratio(AR=input, gamma=1.4)
     self.assertEqual(res, expected_result)
Exemplo n.º 20
0
def Rajeev_complete(p_chamber, T_chamber, w_throat, h_throat, throat_roc,
                    AR_exit, p_back, divergence_half_angle,
                    fp: FluidProperties, is_cold_flow):
    """ Function that implements all corrections proposed by Makhan2018

    Args:
        p_chamber (Pa): Chamber pressure
        T_chamber (K): Chamber temperature
        w_throat (m): Throat width
        h_throat (m): Throat heigh (or channel depth)
        throat_roc (m): Throat radius of curvature
        AR_exit (-): Area ratio of nozzle exit area divided by throat area
        p_back (Pa): Back pressure
        divergence_half_angle (rad): Divergence half angle of nozzle 
        fp (FluidProperties): Object to access fluid properties
        is_cold_flow (bool): Reynolds number is adjusted depending on whether the chamber is heated or cooled

    Raises:
        ValueError: Is raised for hot flow, since no verification is done yet on that equation
    """

    # Get the (assumed to be) constant fluid properties
    gamma = fp.get_specific_heat_ratio(T=T_chamber,
                                       p=p_chamber)  # [-] Specific heat ratio
    R = fp.get_specific_gas_constant()  # [J/kg] Specific gas constant
    # Report calculated values for verification and comparison purposes
    print("Gamma: {:1.4f}".format(gamma))
    print("R: {:3.2f} J/kg\n".format(R))

    # Calculate basic peformance parameters
    A_throat = w_throat * h_throat  # [m] Throat area

    ## IDEAL PERFORMANCE
    # First get ideal performance, and check if the nozzle is properly expanded.
    ep = IRT.get_engine_performance(p_chamber=p_chamber,
                                    T_chamber=T_chamber,
                                    A_throat=A_throat,
                                    AR_exit=AR_exit,
                                    p_back=p_back,
                                    gamma=gamma,
                                    R=R)

    # Report ideal performance
    print("Thrust: {:.2f} mN".format(ep['thrust'] * 1e3))
    print("Isp_ideal: {:.1f} s".format(ep['thrust'] / ep['m_dot'] / 9.80655))
    print("Mass flow: {:.3f} mg/s".format(ep['m_dot'] * 1e6))

    m_dot_ideal = ep['m_dot']  # [kg/s] Ideal mass flow
    #F_ideal = ep['thrust'] # [N] Ideal thrust

    ## CALCULATING THE CORRECTION FACTORS

    # Calculate the divergence loss and report it
    CF_divergence_loss = divergence_loss_conical_2D(
        alpha=divergence_half_angle)
    print("\n -- DIVERGENCE LOSS for {:2.2f} deg divergence half-angle".format(
        math.degrees(divergence_half_angle)))
    print(
        "  Divergence loss (2D concical): {:.5f} ".format(CF_divergence_loss))

    # Calculate the viscous loss

    # To determine the Reynolds number at the throat, the hydraulic diameter at the throat and nozzle conditions must be determined
    # Get hydraulic diameter of the nozzle from the wetted perimeter and nozzle area
    wetted_perimeter_throat = 2 * (w_throat + h_throat
                                   )  # [m] Wetted perimeter throat
    Dh_throat = hydraulic_diameter(A=A_throat,
                                   wetted_perimeter=wetted_perimeter_throat
                                   )  # [m] Hydraulic diameter at throat
    p_throat = p_chamber / IRT.pressure_ratio(
        M=1, gamma=gamma)  # [Pa] pressure in throat
    T_throat = T_chamber / IRT.temperature_ratio(
        M=1, gamma=gamma)  # [K] Temperature in throat
    viscosity_throat = fp.get_viscosity(T=T_throat, p=p_throat)
    # Throat reynolds based on ideal mass flow?
    Re_throat = reynolds(m_dot=m_dot_ideal,
                         A=A_throat,
                         D_hydraulic=Dh_throat,
                         viscosity=viscosity_throat)
    if is_cold_flow:
        Re_throat_wall = Reynolds_throat_wall_cold(reynolds_throat=Re_throat)
    else:
        Re_throat_wall = Reynolds_throat_wall_hot(reynolds_throat=Re_throat)
    print("\n-- THROAT CONDITIONS --")
    print("  p = {:2.4f} bar,     T = {:4.2f} K".format(
        p_throat * 1e-5, T_throat))
    print("  mu = {:2.4f} [microPa*s]  Dh = {:3.4f} [microm]".format(
        viscosity_throat * 1e6, Dh_throat * 1e6))
    print(" Reynolds: {:6.6f} ".format(Re_throat))

    CF_viscous_loss = viscous_loss(area_ratio=AR_exit,
                                   reynolds_throat_wall=Re_throat_wall)
    print(" CF_viscous_loss: {:1.5f}".format(CF_viscous_loss))

    # Calculating throat boundary layer loss, which causes a reduction in effective throat area/mass flow
    Cd_throat_boundary_loss = throat_boundary_loss(gamma=gamma,
                                                   reynolds_throat=Re_throat,
                                                   throat_radius=0.5 *
                                                   Dh_throat,
                                                   throat_roc=throat_roc)
    print("\n-- DISCHARGE FACTOR --")
    print("  Throat boundary layer: {:1.4f}".format(Cd_throat_boundary_loss))

    ## APPLYING THE CORRECTION FACTORS
    # Now all these loss factors must be combined into a new "real" thrust
    # The divergence loss only applies to the jet/momentum thrust and not the pressure, so jet thrust is needed
    # This is equal to the exit velocity times corrected mass flow. The returned exit velocity does not include pressure terms!

    # First we must know the corrected mass flow
    m_dot_real = ep['m_dot'] * Cd_throat_boundary_loss  # [kg/s]
    # Secondly, we must know the pressure thrust to add to the jet thrust again
    F_pressure = IRT.pressure_thrust(p_chamber=p_chamber,
                                     p_back=p_back,
                                     A_throat=A_throat,
                                     AR=AR_exit,
                                     gamma=gamma)
    F_divergence = m_dot_real * ep[
        'u_exit'] * CF_divergence_loss + F_pressure  # [N] Thrust decreased by divergence loss, pressure term must be added again, since divergence only applies to jet thrust
    # This jet thrust is then again corrected by viscous losses, which are subtracted from the current CF
    CF_jet_divergence = F_divergence / (
        p_chamber * A_throat
    )  # [-] Thrust coefficient after taking into account discharge factor and divergence loss
    CF_real_final = CF_jet_divergence - CF_viscous_loss  # [-] The final thrust coefficient, also taking into account viscous loss
    F_real = CF_real_final * p_chamber * A_throat  # [N] Real thrust, after taking into account of all the three proposed correction factors

    # Report "real" results
    print("\n === CORRECTED PERFORMANCE PARAMETERS === ")
    print("  Real mass flow: {:3.4f} mg/s".format(m_dot_real * 1e6))
    print("  CF with divergence loss {:1.5f}".format(CF_jet_divergence))
    print("  Real CF: {:1.5f}".format(CF_real_final))
    print("  Real thrust: {:2.4f} mN".format(F_real * 1e3))
    print("  Real Isp: {:3.2f}".format(F_real / m_dot_real / 9.80655))

    return {
        'm_dot_real': m_dot_real,
        'm_dot_ideal': ep['m_dot'],
        'F_real': CF_real_final
    }
Exemplo n.º 21
0
 def testOne(self):
     # Should return pressure ratio for M=1, as shockwave is infinitely weak
     # Value taken from Anderson2006 Appendix A again
     expected = 0.1893e1
     res = IRT.exit_pressure_ratio_shockwave(exit_area_ratio=1, gamma=1.4)
     self.assertAlmostEqual(res, expected, 3)
Exemplo n.º 22
0
 def test_one(self):
     # Mach number is 1 in the throat so area ratio must be 1.
     expected_result = 1
     res = IRT.area_ratio(M=1, gamma=1.4)
     self.assertEqual(res, expected_result)
Exemplo n.º 23
0
mu_throat = np.zeros_like(m_dot)  # [Pa*s] Dynamic viscosity in the throat

# Results to check condensation in nozzle exit
T_exit = np.zeros_like(m_dot)  # [K]
p_exit = np.zeros_like(m_dot)  # [Pa]

it_AR = np.nditer(AR_exit, flags=['c_index'])

# Iterate over all possible combinations of area ratio and chamber temperature to find the mass flow and area ratio that finds according to basic IRT
for AR in it_AR:
    it_T = np.nditer(T_chamber, flags=['c_index'])
    for T in it_T:
        # Store mass flow, throat area and other results
        ep = IRT.engine_performance_from_F_and_T(F_desired=F,
                                                 p_chamber=p_chamber,
                                                 T_chamber=float(T),
                                                 AR_exit=float(AR),
                                                 p_back=p_back,
                                                 fp=fp)
        m_dot[it_AR.index][it_T.index] = ep['m_dot']  # [kg/s] Mass flow
        A_throat[it_AR.index][it_T.index] = ep['A_throat']  # [m^2] Throat area
        Isp[it_AR.index][it_T.index] = ep['Isp']  # [s] Specific impulse
        h_chamber[it_AR.index][it_T.index] = fp.get_enthalpy(
            T=float(T), p=p_chamber)  # [J/kg] Enthalpy at chamber inlet
        # Throat stuff
        T_throat[it_AR.index][it_T.index] = ep[
            'T_throat']  # [K] Throat temperature
        p_throat[it_AR.index][it_T.index] = ep[
            'p_throat']  # [Pa] Throat pressure
        u_throat[it_AR.index][it_T.index] = ep[
            'u_throat']  # [K] Throat velocity
        Pr_throat[it_AR.index][it_T.index] = fp.get_Prandtl(