Chat
Ask me anything
Ithy Logo
Sign in to save

Axial and Radial Temperature Profile Simulation of Gas in a Hot Pipe

Comprehensive Python Model for Laminar Flow Heat Transfer

gas flowing through heated pipe

Key Takeaways

  • Robust Modeling: Utilizes object-oriented programming for modularity and ease of scalability.
  • Accurate Physical Representation: Incorporates temperature-dependent gas properties using spline interpolations.
  • Comprehensive Visualization: Provides detailed contour plots and bulk temperature profiles to analyze heat transfer efficiency.

Overview

This Python code models the axial and radial temperature profiles of a cold gas mixture entering a hot pipe under laminar flow conditions. The simulation accounts for temperature-dependent gas properties, radiative heat transfer, and ensures unit consistency using SI units for computations and conversions to Celsius and millimeters for outputs and plotting. The modular class-based structure facilitates testing against various flow rates and pipe diameters, enabling the analysis of scaling constraints efficiently.

Complete Python Code

Import Required Libraries

import os
import pickle
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
from scipy.sparse import diags
from scipy.sparse.linalg import spsolve
from dataclasses import dataclass
from typing import Dict, List, Tuple

# Additional libraries
import seaborn as sns

Define Constants

R = 8.314  # Universal gas constant (J/mol·K)

Simulation Parameters

@dataclass
class SimulationParameters:
    """Class to store simulation parameters."""
    gas: Dict[str, float]  # Gas composition as mole fraction
    pipe_temp_C: float     # Pipe wall temperature in Celsius
    inlet_temp_C: float    # Inlet gas temperature in Celsius
    pipe_radius_m: float   # Pipe radius in meters
    pipe_length_m: float   # Pipe length in meters
    flow_rate_SCCM: float  # Flow rate in SCCM (Standard Cubic Centimeters per Minute)
    pressure_Pa: float     # Pressure in Pascals
    axial_steps: int = 100  # Number of axial discretization steps
    radial_steps: int = 50  # Number of radial discretization steps

Gas Properties Handling

class GasProperties:
    """Class to handle gas property calculations based on temperature and pressure."""
    
    def __init__(self, gas_mixture: Dict[str, float], fits_dir: str):
        """
        Initialize GasProperties with gas mixture and directory containing spline fits.

        Parameters:
            gas_mixture (Dict[str, float]): Mole fractions of gases in the mixture.
            fits_dir (str): Directory path where spline fit files are stored.
        """
        self.gas_mixture = gas_mixture
        self.fits_dir = fits_dir
        self.spline_functions = {gas: {} for gas in gas_mixture.keys()}
        self._load_splines()
    
    def _load_splines(self):
        """
        Load spline functions for all required properties of each gas in the mixture.
        Filenames should follow the pattern: {Gas}_{Property}_fits.pkl
        where Property includes units, e.g., "Density_kg_per_m3".
        """
        required_properties = {
            'density': 'Density_kg_per_m3',
            'viscosity': 'Viscosity_Pa_s',
            'specific_heat': 'Specific_Heat_J_per_kg_K',
            'thermal_conductivity': 'Thermal_Conductivity_W_per_m_K',
            'prandtl': 'Prandtl_Number'
        }
        
        for gas in self.gas_mixture:
            for prop, prop_filename in required_properties.items():
                fit_filename = f"{gas}_{prop_filename}_fits.pkl"
                fit_path = os.path.join(self.fits_dir, fit_filename)
                if not os.path.isfile(fit_path):
                    raise FileNotFoundError(f"Spline fit file not found: {fit_path}")
                with open(fit_path, 'rb') as f:
                    fit_objects = pickle.load(f)
                    spline = fit_objects.get('spline')
                    if spline is None:
                        raise ValueError(f"Spline function not found in {fit_path}")
                    self.spline_functions[gas][prop] = spline
        print(f"Spline functions loaded for mixture: {self.gas_mixture}")
    
    def get_properties(self, T: float, P: float) -> Dict[str, float]:
        """
        Retrieve mixture properties at a specified temperature and pressure using spline interpolation.

        Parameters:
            T (float): Temperature in Kelvin.
            P (float): Pressure in Pascal.

        Returns:
            Dict[str, float]: Dictionary containing mixture properties.
        """
        delta_T = T - 273.15  # Delta T from absolute temperature
        T_C = delta_T  # Temperature in Celsius

        # Temperature range check
        if T_C < 100 or T_C > 1500:
            print(f"Warning: Temperature {T_C}°C is outside the fitted range (100°C to 1500°C).")
        
        mixture_props = {
            'density': 0.0,
            'viscosity': 0.0,
            'specific_heat': 0.0,
            'thermal_conductivity': 0.0,
            'prandtl': 0.0
        }
        
        fraction_type = {
            'density': 'mass',
            'specific_heat': 'mass',
            'viscosity': 'mole',
            'thermal_conductivity': 'mole',
            'prandtl': 'mole'
        }
    
        MWs = {
            'Methane': 16.04,
            'Carbon_Dioxide': 44.01,
            'Water_Steam': 18.02,
            'Hydrogen': 2.02
        }
    
        MW_mixture = {gas: MWs[gas] for gas in self.gas_mixture.keys()}
    
        # Calculate the average molecular weight of the mixture
        average_molecular_weight = sum(
            mole_frac * MW_mixture[gas] for gas, mole_frac in self.gas_mixture.items()
        )
    
        # Calculate mass fractions
        mass_fractions = {
            gas: (mole_frac * MW_mixture[gas]) / average_molecular_weight
            for gas, mole_frac in self.gas_mixture.items()
        }
    
        # Loop through the properties and update using the appropriate fraction type
        for gas, mole_frac in self.gas_mixture.items():
            mass_frac = mass_fractions[gas]
            for prop, spline in self.spline_functions[gas].items():
                T_eval = T_C
                try:
                    value = spline(T_eval)
                except Exception as e:
                    raise ValueError(f"Error evaluating spline for '{prop}' of '{gas}' at {T_C}°C: {e}")
    
                # Choose the appropriate fraction type
                if fraction_type[prop] == 'mass':
                    mixture_props[prop] += mass_frac * value
                elif fraction_type[prop] == 'mole':
                    mixture_props[prop] += mole_frac * value
                else:
                    raise ValueError(f"Unknown fraction type for property '{prop}'.")
    
        return mixture_props

Pipe Flow Simulation

class PipeFlowSimulation:
    """Class to perform pipe flow temperature simulation."""
    
    def __init__(self, params: SimulationParameters, gas_props: GasProperties, radiative_power_W_per_cm: float = 0.0):
        """
        Initialize the simulation with parameters and gas properties.

        Parameters:
            params (SimulationParameters): Simulation parameters.
            gas_props (GasProperties): Gas properties handler.
            radiative_power_W_per_cm (float): Radiative power per unit length in Watts/cm.
        """
        self.params = params
        self.gas_props = gas_props
        self.radiative_power_W_per_cm = radiative_power_W_per_cm
        self.axial_steps = params.axial_steps
        self.radial_steps = params.radial_steps
        self.dz = params.pipe_length_m / self.axial_steps  # Axial step size in meters
        self.dr_initial = params.pipe_radius_m / 100  # Initial radial step size (for geometric scaling)
        self.r = self.initialize_radial_mesh()
        self.z = np.linspace(0, params.pipe_length_m, self.axial_steps +1)
        self.T = np.ones((self.radial_steps +1, self.axial_steps +1)) * (params.inlet_temp_C + 273.15)  # Temperature in Kelvin
        self.u = np.zeros((self.radial_steps +1, self.axial_steps +1))  # Velocity in m/s
        self.P = params.pressure_Pa
        self.flow_rate_m3_s = params.flow_rate_SCCM * 1.66667e-8  # Convert SCCM to m³/s
        self.initialize_velocity_profile()
        self.calculate_velocity_profile()
    
    def initialize_radial_mesh(self):
        """
        Initialize radial mesh with geometric spacing from the wall.

        Returns:
            np.ndarray: Radial positions in meters.
        """
        ratio = 1.1  # Geometric progression ratio
        r = np.zeros(self.radial_steps +1)
        for i in range(1, self.radial_steps +1):
            r[i] = r[i-1] + self.dr_initial * ratio <b> (i-1)
        return r
    
    def initialize_velocity_profile(self):
        """
        Initialize velocity profile based on entrance conditions.
        """
        # Assuming initial laminar parabolic profile
        A = np.pi * self.params.pipe_radius_m</b>2
        u_avg = self.flow_rate_m3_s / A
        for i in range(self.axial_steps +1):
            for j in range(self.radial_steps +1):
                r_pos = self.r[j]
                self.u[j, i] = 2 * u_avg * (1 - (r_pos / self.params.pipe_radius_m)<b>2)
    
    def calculate_velocity_profile(self):
        """
        Update velocity profile considering temperature changes.
        (In this simplified model, assuming velocity profile remains parabolic)
        """
        # For laminar flow under constant pressure, velocity profile shape remains parabolic
        # However, viscosity varies with temperature, so adjust velocity accordingly
        for i in range(self.axial_steps +1):
            # Compute Reynolds number based on local temperature
            T_bulk = np.mean(self.T[:, i])
            props = self.gas_props.get_properties(T_bulk, self.P)
            viscosity = props['viscosity']  # Pa·s
            density = props['density']      # kg/m³
            Re = (density * (2 * self.flow_rate_m3_s / (np.pi * self.params.pipe_radius_m</b>2)) * self.params.pipe_radius_m) / viscosity
            if Re > 2300:
                print(f"Warning: Flow may not be laminar at axial step {i} with Re={Re}")
            # Update velocity profile
            u_avg = self.flow_rate_m3_s / (np.pi * self.params.pipe_radius_m<b>2)
            for j in range(self.radial_steps +1):
                r_pos = self.r[j]
                self.u[j, i] = 2 * u_avg * (1 - (r_pos / self.params.pipe_radius_m)</b>2)
    
    def solve_temperature_profile(self):
        """
        Solve the temperature profile using finite difference method.
        """
        # Iterate over axial steps
        for i in range(1, self.axial_steps +1):
            for j in range(1, self.radial_steps):
                # Current temperatures
                T_current = self.T[j, i-1]
                T_up = self.T[j+1, i-1]
                T_down = self.T[j-1, i-1]
                T_wall = self.params.pipe_temp_C + 273.15  # Wall temperature in Kelvin
                
                # Get properties at current temperature
                props = self.gas_props.get_properties(T_current, self.P)
                alpha = props['thermal_conductivity'] / (props['density'] * props['specific_heat'])  # Thermal diffusivity
                
                # Finite difference approximation
                delta_T = alpha * self.dz / (self.u[j, i-1] * self.dr_initial<b>2) * (T_up - 2*T_current + T_down)
                
                # Update temperature
                self.T[j, i] = T_current + delta_T
            
            # Apply boundary conditions
            self.T[0, i] = self.T[1, i]  # Symmetry at center
            self.T[self.radial_steps, i] = T_wall  # Wall temperature
        
    def add_radiative_power(self, i: int):
        """
        Add radiative power to the temperature at each axial step.

        Parameters:
            i (int): Current axial index.
        """
        dz = self.dz
        power_step = self.radiative_power_W_per_cm * 100 * dz  # Convert W/cm to W/m, multiply by dz
        volume = np.pi * (self.r[-1]</b>2 - self.r[0]<b>2) * dz
        delta_T = power_step / (self.gas_props.get_properties(np.mean(self.T[:,i]), self.P)['density'] * np.pi * self.params.pipe_radius_m</b>2 * self.gas_props.get_properties(np.mean(self.T[:,i]), self.P)['specific_heat'])
        for j in range(self.radial_steps +1):
            self.T[j, i] += delta_T
    
    def run_simulation(self):
        """
        Run the complete simulation.
        """
        for i in range(1, self.axial_steps +1):
            self.solve_temperature_profile()
            if self.radiative_power_W_per_cm > 0:
                self.add_radiative_power(i)
            self.calculate_velocity_profile()
    
    def plot_results(self):
        """
        Plot the results: Temperature contour and bulk temperature profile.
        """
        # Convert radial and axial positions to mm and C for plotting
        r_mm = self.r * 1000  # meters to millimeters
        z_cm = self.z * 100  # meters to centimeters
        T_C = self.T - 273.15   # Kelvin to Celsius
        
        # Temperature contour plot
        plt.figure(figsize=(14,6))
        plt.subplot(1,2,1)
        Z, R = np.meshgrid(z_cm, r_mm)
        contour = plt.contourf(Z, R, T_C, levels=50, cmap='hot')
        plt.colorbar(contour, label='Temperature (°C)')
        plt.xlabel('Axial Position (cm)')
        plt.ylabel('Radial Position (mm)')
        plt.title('Temperature Distribution in Pipe')
        
        # Bulk temperature profile
        plt.subplot(1,2,2)
        T_bulk = np.mean(T_C, axis=0)
        plt.plot(z_cm, T_bulk, label='Bulk Temperature')
        plt.axhline(y=self.params.pipe_temp_C, color='r', linestyle='--', label='Pipe Temperature')
        plt.xlabel('Axial Position (cm)')
        plt.ylabel('Bulk Temperature (°C)')
        plt.title('Bulk Temperature vs Axial Position')
        plt.legend()
        plt.grid(True)
        
        plt.tight_layout()
        plt.show()
    
    def calculate_total_power(self):
        """
        Calculate the total power added to the gas.

        Returns:
            float: Total power in Watts.
        """
        total_power = self.radiative_power_W_per_cm * 100 * self.params.pipe_length_m  # W/cm * cm = Watts
        print(f"Total power added to the gas: {total_power} W")
        return total_power

Example Usage

if __name__ == "__main__":
    # Define gas mixture
    gas_mixture = {
        "Methane": 0.30,
        "Water_Steam": 0.60,
        "Hydrogen": 0.10
    }
    
    # Initialize simulation parameters
    params = SimulationParameters(
        gas=gas_mixture,
        pipe_temp_C=800,
        inlet_temp_C=100,
        pipe_radius_m=5.2/1000/2,  # Pipe radius in meters
        pipe_length_m=50/100,      # Pipe length in meters
        flow_rate_SCCM=1700,
        pressure_Pa=101325
    )
    
    # Initialize gas properties
    gas_props = GasProperties(gas_mixture, fits_dir='path_to_spline_fits')
    
    # Initialize Pipe Flow Simulation with radiative power of 1.0 W/cm
    simulation = PipeFlowSimulation(params, gas_props, radiative_power_W_per_cm=1.0)
    
    # Run simulation
    simulation.run_simulation()
    
    # Calculate total power added
    simulation.calculate_total_power()
    
    # Plot results
    simulation.plot_results()

Conclusion

This Python code provides a comprehensive and robust simulation of the axial and radial temperature profiles of a cold gas mixture flowing through a heated pipe under laminar conditions. By incorporating temperature-dependent gas properties, radiative heat transfer, and ensuring unit consistency, the model allows for efficient scaling and analysis of different flow rates and pipe dimensions. The visualization of results through contour and bulk temperature plots aids in understanding heat transfer efficiency and identifying scale constraints effectively.


Last updated January 19, 2025

AI-assisted research. Sources, methodology and corrections · Source-checked guides and tools · Explore published answers

Ask Ithy AI
Download Article
Delete Article