Example #1
0
    def setUp(self):
        # Results based on manual calculation in excelsheet Verification_one_d.xlsx
        fp = FluidProperties('water')
        p_inlet = 2e5  # [Pa] Inlet pressure
        steps = 3  # [-] Number of data point
        m_dot = 0.1e-3  # [kg/s]
        T_wall = 500  # [K]
        T_inlet = 300  # [K]
        T_outlet = 450
        channel_amount = 1

        # Nusselt functions
        Nusselt_relations = {
            'Nu_func_gas': thermo.convection.
            Nu_DB,  # [-] Function to calculate Nusselt number (gas phase)
            'Nu_func_liquid': thermo.convection.
            Nu_DB,  # [-] Function to caculate Nusselt number (liquid phase)
            'Nu_func_two_phase': tp.
            Nu_Kandlikar_NBD_dryout,  # [-] Function to calculate Nusselt number (two-phase)
            'Nu_func_le': thermo.convection.
            Nu_DB,  # [-] Function to calculate Nusselt in two-phase, AS IF the flow was entirely liquid (two-phase, le)
            'Nu_func_dryout': tp.
            Nu_DB_two_phase,  #thermo.two_phase.Nu_DB_two_phase # [-] Function to calculate the Nusselt number after dry-out. It is up to Nu_func_two_phase to decide if/how to apply it'
        }

        # Pressure drop functions. Testing 3 seperate functions to check integraotin, modularity, as well as separate functions
        pressure_drop_relations = {
            'l':
            oneD.calc_single_phase_frictional_pressure_drop_low_Reynolds,
            'tp':
            oneD.calc_two_phase_frictional_pressure_drop_low_Reynolds,
            'g':
            oneD.calc_single_phase_frictional_pressure_drop_high_Reynolds,
            'contraction':
            oneD.calc_single_phase_contraction_pressure_drop_Kawahara2015,
        }

        #Geometry
        w_channel = 100e-6  # [m] Width and height
        h_channel = 100e-6  # [m] Channel height
        area_ratio_contraction = 0  # [-] Arbitrary input for contraction pressure drop
        steps = 3

        self.prepared_values = oneD.full_homogenous_preparation(\
            T_inlet=T_inlet,
            T_outlet=T_outlet,
            m_dot=m_dot,
            p_ref=p_inlet,
            steps_l=steps,
            steps_tp=steps,
            steps_g=steps,
            fp=fp)

        self.res = oneD.rectangular_multi_channel_homogenous_calculation(\
            channel_amount=channel_amount,
            prepared_values=self.prepared_values,
            Nusselt_relations=Nusselt_relations,
            pressure_drop_relations=pressure_drop_relations,
            w_channel=w_channel,
            h_channel=h_channel,
            m_dot=m_dot,
            T_wall=T_wall,
            p_inlet=p_inlet,
            fp=fp,
            area_ratio_contraction=area_ratio_contraction)
        return super().setUp()
Example #2
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
Example #3
0
def calc_and_plot_channel_width(w_channel, ax_P_loss, fig_P_loss, ax_w_total, fig_w_total, ax_l_total, fig_l_total, ax_pressure_drop, fig_pressure_drop, ax_is_choked, fig_is_choked,\
    ax_Re_channel_exit, fig_Re_channel_exit):
    # Taking some thruster data to make the optmization a bit similar to existing cases
    td = None  #thrusters.thruster_data.Cen2010_6

    # Desired chamber outlet/nozzle inlet conditions
    # Switch between an existing thruster or a desired one
    if td is not None:
        m_dot = td['m_dot']  # [kg/s] Mass flow
        T_chamber = td['T_chamber_guess']  # [K] Chamber temperature
        T_inlet = 300  # [K] Inlet temperature
        p_inlet = td[
            'p_inlet']  # [Pa] Inlet pressure, assumed to be constant through-out the channel
        F_desired = 7.71e-3  # td['F'] # [N] Thrust
        p_back = td['p_back']
        fp = FluidProperties(
            td['propellant'])  # Object to access fluid properties
        AR_exit = td['AR_exit']  # [-] Exit area of nozzle
        w_throat = td['w_throat']  # [m] Throat width
        #w_channel = 1.7*td['w_channel'] #  [m] Channel width
        inlet_manifold_length_factor = 2  # [m] Multiplication factor with inlet manifold width to determine manifold length
        inlet_manifold_width_factor = 5.5  # [-] Multiplication factor (with channel width to determine margin in chamber)
        l_exit_manifold = 1e-3  # [m] Length between the end of multiple channels and start of convergent nozzle
        convergent_half_angle = td[
            'convergent_half_angle']  # [rad] Half-angle of the convergent part of the nozzle
        divergent_half_angle = td[
            'divergent_half_angle']  # [rad] Half-angle of divergent part of the nozzle
        w_channel_spacing = td[
            'w_channel_spacing']  # [m] Spacing between channels (wall-to-wall)
        w_outer_margin = 2e-3  # [m] Margin around the outer channels for structural integrity
        channel_amount = td['channel_amount']  # [-] Number of channels
        h_channel = td['h_channel']  # [m] Channel depth
        emissivity_chip_top = 1  # [-] Assumed emissivity of chip at top-side

    else:
        F_desired = 10e-3  # [N] Desired thrust
        p_inlet = 5.0e5  # [Pa] Inlet pressure
        T_inlet = 300  # [K] Inlet temperature
        T_chamber = 500  # [K] Chamber temperature
        p_back = 0  # [Pa] Back pressure
        AR_exit = 10  # [-] Exit area ratio
        fp = FluidProperties(
            "water")  # (obj) Object to access water properties
        h_channel = 100e-6  # [m] Channel depth
        #w_channel = 25e-6 #  [m] Channel width

        # Find the mass flow and throat area IRT requires
        ep_ideal = 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)
        m_dot = ep_ideal['m_dot']  # [kg/s] Mass flow
        w_throat = ep_ideal['A_throat'] / h_channel  # [m] Throat width
        Isp = ep_ideal['thrust'] / ep_ideal['m_dot'] / physical_constants.g0
        # Print ideal values
        print("\n --- IDEAL PARAMETERS ---")
        print(" Thrust: {:1.1f} mN".format(ep_ideal['thrust'] * 1e3))
        print(" Isp: {:3.1f} s".format(Isp))
        print(" Mass flow: {:3.3f} mg/s".format(m_dot * 1e6))
        print(" Throat width: {:4.1f} micron".format(w_throat * 1e6))

        # Remaining paramters
        inlet_manifold_length_factor = 2  # [m] Multiplication factor with inlet manifold width to determine manifold length
        inlet_manifold_width_factor = 5.5  # [-] Multiplication factor (with channel width to determine margin in chamber)
        l_exit_manifold = 0  # [m] Length between the end of multiple channels and start of convergent nozzle
        convergent_half_angle = math.radians(
            45)  # [rad] Half-angle of the convergent part of the nozzle
        divergent_half_angle = math.radians(
            22.5)  # [rad] Half-angle of divergent part of the nozzle
        w_channel_spacing = 0.5e-5  # [m] Spacing between channels (wall-to-wall)
        w_outer_margin = 2e-3  # [m] Margin around the outer channels for structural integrity
        channel_amount = 20  # [-] Number of channels
        emissivity_chip_top = 0.5  # [-] Assumed emissivity of chip at top-side
        emissivity_chip_bottom = 0.5  # [-]

        # Wall effects
        kappa_wall = 100  # [W/(m*K)]Thermal conductivity of the silicon walls
        #T_wall_bottom = T_chamber
        wall_args = {
            'kappa_wall': kappa_wall,
            #    'T_wall_bottom': T_wall_bottom,
            'h_channel': h_channel,
            'w_channel': w_channel,
            'w_channel_spacing': w_channel_spacing,
            'emissivity_chip_bottom': emissivity_chip_bottom,
        }

    # Desired geometric values

    # Used heat transfer relations
    Nusselt_relations = {
        'Nu_func_gas':
        thermo.convection.
        Nu_laminar_developed_constant_wall_temp_square,  # [-] Function to calculate Nusselt number (gas phase)
        'Nu_func_liquid':
        thermo.convection.
        Nu_laminar_developed_constant_wall_temp_square,  # [-] Function to caculate Nusselt number (liquid phase)
        'Nu_func_two_phase':
        tp.
        Nu_Kandlikar_NBD_dryout,  # [-] Function to calculate Nusselt number (two-phase)
        'Nu_func_le':
        thermo.convection.
        Nu_laminar_developed_constant_wall_temp_square,  # [-] Function to calculate Nusselt in two-phase, AS IF the flow was entirely liquid (two-phase, le)
        'Nu_func_dryout':
        thermo.convection.
        Nu_laminar_developed_constant_wall_temp_square,  #thermo.two_phase.Nu_DB_two_phase # [-] Function to calculate the Nusselt number after dry-out. It is up to Nu_func_two_phase to decide if/how to apply it'
    }

    pressure_drop_relations = {
        'l':
        oneD.calc_single_phase_frictional_pressure_drop_low_Reynolds,
        'tp':
        oneD.calc_two_phase_frictional_pressure_drop_low_Reynolds,
        'g':
        oneD.calc_single_phase_frictional_pressure_drop_low_Reynolds,
        'contraction':
        oneD.calc_single_phase_contraction_pressure_drop_Kawahara2015
    }

    # Fidelity of simulation
    steps_per_section = 100  # [-] Amount of subdivision in each section
    steps_l = steps_per_section
    steps_tp = steps_per_section
    steps_g = steps_per_section

    prepared_values = oneD.full_homogenous_preparation(
        T_inlet=T_inlet,
        T_outlet=
        T_chamber,  # Outlet of the channel is equal to chamber temperature in IRT
        m_dot=m_dot / channel_amount,
        p_ref=p_inlet,  # The pressure in the channel is assumed to be constant
        steps_l=steps_l,
        steps_tp=steps_tp,
        steps_g=steps_g,
        fp=fp)

    # Power loss function to optimzie
    func_P_loss = lambda T_superheat: optim_P_total(
        channel_amount=channel_amount,
        w_channel=w_channel,
        h_channel=h_channel,
        inlet_manifold_length_factor=inlet_manifold_length_factor,
        inlet_manifold_width_factor=inlet_manifold_width_factor,
        l_exit_manifold=l_exit_manifold,
        w_channel_spacing=w_channel_spacing,
        w_outer_margin=w_outer_margin,
        T_wall=T_chamber +
        T_superheat,  # [K] Wall temperature must of course be higher than chamber temperature, so a variable T_superheater that must be larger than 0 is useful
        p_ref=p_inlet,
        m_dot=m_dot,
        prepared_values=prepared_values,
        Nusselt_relations=Nusselt_relations,
        pressure_drop_relations=pressure_drop_relations,
        convergent_half_angle=convergent_half_angle,
        divergent_half_angle=divergent_half_angle,
        F_desired=F_desired,
        p_back=p_back,
        AR_exit=AR_exit,
        w_throat=w_throat,
        emissivity_top=emissivity_chip_top,
        fp=fp,
        wall_args=wall_args)

    # Fist make a plot to see what's going on
    T_range = np.linspace(start=50, stop=200, num=200)  # [K]

    # Store calculated heat loss here
    P_loss = np.zeros_like(T_range) * np.nan  # [W] Heat loss
    l_channel = np.zeros_like(
        T_range) * np.nan  # [m] Heating channel length (not the chip length)
    l_total = np.zeros_like(T_range) * np.nan  # [m] Total chip length
    w_total = np.zeros_like(T_range) * np.nan  # [m] Total chip width.
    pressure_drop = np.zeros_like(
        T_range
    ) * np.nan  # [Pa] Pressure drop over channel (only if it drops stagnation pressure)
    is_channel_choked = np.zeros_like(
        T_range) * np.nan  # [-] Going to be 0 if not choked, and 1 if chocked
    Re_channel_exit = np.zeros_like(
        T_range) * np.nan  # [-] Reynolds number at throat
    dP_contraction = np.zeros_like(
        T_range
    ) * np.nan  # [Pa] Pressure drop over sudden contraction at entrance
    #F = np.zeros_like(T_range) # [N] To store thrust that is iterated to, for debugging purposes
    #CF = np.zeros_like(T_range) # [-] To store thrust coefficient

    iter_T = np.nditer(T_range, flags=['c_index'])

    for T in iter_T:
        #print("\n\n --- RUN {} ---- ".format(iter_T.index))
        res = func_P_loss(T)

        P_loss[iter_T.index] = res['P_loss']
        l_channel[iter_T.index] = res['l_channel']
        pressure_drop[iter_T.index] = res['pressure_drop']
        dP_contraction[iter_T.index] = res['dP_contraction']
        l_total[iter_T.index] = res['l_total']
        w_total[iter_T.index] = res['w_total']

        # Check if channels are choked?
        is_channel_choked[iter_T.index] = res['is_channel_choked']  # []
        # See if Reynolds number still makes sense (laminar or turbulent?)
        Re_channel_exit[iter_T.index] = res['Re_channel_exit']

    print("Minumum heat loss: {:2.3f} W".format(np.nanmin(P_loss)))

    #ax2 = ax1.twinx()
    ax_P_loss.plot(T_range + T_chamber,
                   P_loss,
                   label="{:3.1f}".format(w_channel * 1e6))

    fig_P_loss.suptitle(
        "Heat loss ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)"
        .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber))
    ax_P_loss.set_xlabel("Wall temp [K]")
    ax_P_loss.set_ylabel("Heat loss [W]")

    ax_l_total.plot(T_range + T_chamber,
                    l_channel * 1e3,
                    label="{:3.1f}".format(w_channel * 1e6))
    ax_l_total.set_ylabel("Channel length [mm]")
    ax_l_total.set_xlabel("Wall temp [K]")
    fig_l_total.suptitle(
        "Channel length ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)"
        .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber))

    ax_pressure_drop.plot(T_range + T_chamber,
                          pressure_drop * 1e-5,
                          label="{:3.1f}".format(w_channel * 1e6))
    ax_pressure_drop.set_xlabel("Wall temp [K]")
    ax_pressure_drop.set_ylabel("Pressure drop [bar]")
    fig_pressure_drop.suptitle(
        "Pressure drop ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)"
        .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber))

    ax_is_choked.plot(T_range + T_chamber,
                      is_channel_choked,
                      marker='o',
                      linestyle=" ",
                      label="{:3.1f}".format(w_channel * 1e6))
    ax_is_choked.set_xlabel("Wall temp [K]")
    ax_is_choked.set_ylabel("Choked ?")
    fig_is_choked.suptitle(
        "Choked? ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)"
        .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber))

    ax_w_total.plot(T_range + T_chamber,
                    w_total * 1e3,
                    label="{:3.1f}".format(w_channel * 1e6))
    ax_w_total.set_xlabel("Wall temp [K]")
    ax_w_total.set_ylabel("Chip width [mm] ")
    fig_w_total.suptitle(
        "Chip width ($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)"
        .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber))

    ax_Re_channel_exit.plot(T_range + T_chamber,
                            Re_channel_exit,
                            label="{:3.1f}".format(w_channel * 1e6))
    ax_Re_channel_exit.set_xlabel("Wall temp [K]")
    ax_Re_channel_exit.set_ylabel("Reynolds Channel [mm] ")
    fig_Re_channel_exit.suptitle(
        "Reynolds channel($\\dot{{m}}={:3.2f}$ mg$\\cdot$s$^{{-1}}$, $p={:1.2f}$ bar, $T={:3.0f}$ K)"
        .format(m_dot * 1e6, p_inlet * 1e-5, T_chamber))
Example #4
0
def calc_and_plot_thruster(td, axs_to_plot):
    # For Cen the chamber temperature is unknown, so  a range is taken instead
    # seem inconsitent with saturation temperatures and/or reported wall temperatures
    T_wall = td['T_wall']                   # [K] Wall temperature
    w_channel = td['w_channel']             # [m] Channel width
    T_inlet = td['T_inlet']                 # [K] Inlet temperature
    p_inlet = td['p_inlet']                 # [Pa] Inlet pressure
    m_dot = td['m_dot']                     # [kg/s] Mass flow (through all channels if multiple)
    channel_amount = td['channel_amount']   # [-] Amount of channels
    h_channel = td['h_channel']             # [m] Channel height/depth
    fp = FluidProperties(td['propellant'])  # Object from which fluid properties can be accessed

    # Calculate mass flow for one single channel
    m_dot_channel = m_dot/channel_amount    # [kg/s] Mass flow through one single channel

    # Chamber temperature is unknown, so a range is taken
    T_sat = fp.get_saturation_temperature(p=p_inlet) # [K]
    T_chamber = np.linspace(start=T_sat+1, stop=T_wall-1, num=250) # [K] 

    # Geometric values
    wetted_perimeter = basic.chamber.wetted_perimeter_rectangular(w_channel=w_channel, h_channel=h_channel) # [m] Wetted perimeter of channel
    A_channel = w_channel*h_channel # [m^2] Cross-sectional through which fluid flows
    D_hydraulic = basic.chamber.hydraulic_diameter_rectangular(w_channel=w_channel, h_channel=h_channel) # [m] Hydraulic diameter
    # Preparation functions calculations many intermediate values that are known before geometry is known


    
    # Storing the length results in here, one for each set of Nusselt relations
    L_1 = np.zeros_like(T_chamber) # [m] Total channel length
    L_2 = np.zeros_like(L_1)

    # Loop to calculate the channel length with each wall temperature
    it_T = np.nditer(T_chamber, flags=['c_index']) # [K] Wall temperature
    T_chamber_guess = None # [K] Stores chamber temperature when actual length is first reached (in laminar case).
    for T in it_T:


        prepared_values = oneD.full_homogenous_preparation(
        T_inlet=T_inlet,
        T_outlet=T, # <---- Iterated variable
        m_dot=m_dot_channel,
        p_ref=p_inlet,
        steps_l=steps_l,
        steps_tp=steps_tp,
        steps_g=steps_g,
        fp=fp)

        # results_1 = oneD.full_homogenous_calculation(
        #     prepared_values=prepared_values,
        #     Nusselt_relations=Nusselt_relations_1,
        #     A_channel=A_channel,
        #     wetted_perimeter=wetted_perimeter,
        #     D_hydraulic=D_hydraulic,
        #     m_dot=m_dot,
        #     T_wall=T_wall,
        #     p_ref=p_inlet,
        #     fp=fp
        #     )
        
        results_2 = oneD.full_homogenous_calculation(
            prepared_values=prepared_values,
            Nusselt_relations=Nusselt_relations_2,
            A_channel=A_channel,
            wetted_perimeter=wetted_perimeter,
            D_hydraulic=D_hydraulic,
            m_dot=m_dot_channel,
            T_wall=T_wall, # <--- Iterated variable
            p_ref=p_inlet,
            fp=fp
            )
        
        # First time the length crosses the actual length, store the value
        if (T_chamber_guess == None) and (results_2['L_total'] > td['L_channel']): # results_2 stores laminar results
            T_chamber_guess = T # [K]
        # L_1[it_T.index] = results_1['L_total']
        L_2[it_T.index] = results_2['L_total']

    ## Print info about thruster, including, estimated chamber temperature for laminar relations
    print("Thruster name: {}".format(td['name']))
    print("Estimated chamber temperature: {:3.2f} K".format(T_chamber_guess))
    

    axs_to_plot.set_title("$\\dot{{m}}={:1.2f}$ mg/s, $p={:1.2f}$ bar".format(m_dot*1e6,p_inlet*1e-5))
    # axs_to_plot.plot(T_chamber,L_1*1e3, label="Turbulent")
    axs_to_plot.plot(T_chamber,L_2*1e3, label="Laminar")
    axs_to_plot.hlines(td['L_channel']*1e3, xmin=T_chamber[0], xmax=T_chamber[-1], linestyle='dashed', color='red', label="Real length")
    axs_to_plot.grid()
Example #5
0
# Wall temperature is unknown, so a range is taken
T_wall = np.linspace(start=T_chamber + 0.01, stop=600, num=5000)  # [K]

# Geometric values
wetted_perimeter = basic.chamber.wetted_perimeter_rectangular(
    w_channel=w_channel,
    h_channel=h_channel)  # [m] Wetted perimeter of channel
A_channel = w_channel * h_channel  # [m^2] Cross-sectional through which fluid flows
D_hydraulic = basic.chamber.hydraulic_diameter_rectangular(
    w_channel=w_channel, h_channel=h_channel)  # [m] Hydraulic diameter
# Preparation functions calculations many intermediate values that are known before geometry is known
prepared_values = oneD.full_homogenous_preparation(T_inlet=T_inlet,
                                                   T_outlet=T_chamber,
                                                   m_dot=m_dot,
                                                   p_ref=p_inlet,
                                                   steps_l=steps_l,
                                                   steps_tp=steps_tp,
                                                   steps_g=steps_g,
                                                   fp=fp)

# Storing the length results in here, one for each set of Nusselt relations
L_1 = np.zeros_like(T_wall)  # [m] Total channel length
L_2 = np.zeros_like(L_1)

# Loop to calculate the channel length with each wall temperature
it_T = np.nditer(T_wall, flags=['c_index'])  # [K] Wall temperature

for T in it_T:
    # results_1 = oneD.full_homogenous_calculation(
    #     prepared_values=prepared_values,
    #     Nusselt_relations=Nusselt_relations_1,