Axial and Radial Temperature Profile Simulation of Gas in a Hot Pipe
Comprehensive Python Model for Laminar Flow Heat Transfer
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