Source code for sproclib.unit.distillation.tray.DistillationTray

"""
Distillation Tray Model for SPROCLIB

This module provides a model for individual distillation trays
in binary systems with vapor-liquid equilibrium.

Author: Thorsten Gressling <gressling@paramus.ai>
License: MIT License
"""

import numpy as np
import logging
from ...base import ProcessModel

logger = logging.getLogger(__name__)


[docs] class DistillationTray(ProcessModel): """Individual distillation tray model for binary systems."""
[docs] def __init__( self, tray_number: int = 1, holdup: float = 1.0, # Liquid holdup [kmol] alpha: float = 2.5, # Relative volatility (-) name: str = "DistillationTray" ): """ Initialize distillation tray. Args: tray_number: Tray number (1 = top) holdup: Liquid holdup on tray [kmol] alpha: Relative volatility (light/heavy) [-] name: Model name """ super().__init__(name) self.tray_number = tray_number self.holdup = holdup self.alpha = alpha self.parameters = { 'tray_number': tray_number, 'holdup': holdup, 'alpha': alpha }
[docs] def vapor_liquid_equilibrium(self, x: float) -> float: """ Calculate vapor composition using relative volatility. Args: x: Liquid mole fraction of light component Returns: Vapor mole fraction of light component """ return self.alpha * x / (1 + (self.alpha - 1) * x)
[docs] def describe(self) -> dict: """ Introspect metadata for documentation and algorithm querying. Returns: dict: Metadata about the model including algorithms, parameters, equations, and usage information. """ return { 'type': 'DistillationTray', 'description': 'Individual distillation tray model with vapor-liquid equilibrium for binary component separation', 'category': 'unit/separation/distillation', 'algorithms': { 'vapor_liquid_equilibrium': 'y = α*x / (1 + (α-1)*x) - Relative volatility VLE model', 'material_balance': 'dN*x/dt = F_in*x_in - F_out*x_out - Accumulation-based component balance' }, 'parameters': { 'tray_number': { 'value': self.tray_number, 'units': 'dimensionless', 'description': 'Tray position in column (1 = top)' }, 'holdup': { 'value': self.holdup, 'units': 'kmol', 'description': 'Liquid molar holdup on tray' }, 'alpha': { 'value': self.alpha, 'units': 'dimensionless', 'description': 'Relative volatility (light/heavy component)' } }, 'state_variables': ['x_tray'], 'inputs': ['L_in', 'x_in', 'V_in', 'y_in', 'L_out', 'V_out'], 'outputs': ['x_tray', 'y_tray'], 'valid_ranges': { 'holdup': {'min': 0.1, 'max': 100.0, 'units': 'kmol'}, 'alpha': {'min': 1.01, 'max': 20.0, 'units': 'dimensionless'}, 'composition': {'min': 0.0, 'max': 1.0, 'units': 'mole_fraction'} }, 'applications': ['Binary distillation columns', 'Absorption towers', 'Stripping columns', 'Rectification processes'], 'limitations': ['Binary systems only', 'Constant relative volatility', 'Equilibrium stages assumed', 'No tray efficiency factors'] }
[docs] def dynamics(self, t: float, x: np.ndarray, u: np.ndarray) -> np.ndarray: """ Tray dynamics: component material balance. State variables: x[0]: Liquid mole fraction of light component on tray Input variables: u[0]: Liquid flow rate from tray above [kmol/min] u[1]: Liquid composition from tray above [mole fraction] u[2]: Vapor flow rate from tray below [kmol/min] u[3]: Vapor composition from tray below [mole fraction] u[4]: Liquid flow rate from this tray [kmol/min] u[5]: Vapor flow rate from this tray [kmol/min] Args: t: Time x: [x_tray] - liquid composition on tray u: [L_in, x_in, V_in, y_in, L_out, V_out] Returns: [dx_tray/dt] """ x_tray = max(0.0, min(1.0, x[0])) # Constrain to [0,1] L_in = u[0] # Liquid in from above x_in = u[1] # Liquid composition in V_in = u[2] # Vapor in from below y_in = u[3] # Vapor composition in L_out = u[4] # Liquid out V_out = u[5] # Vapor out # Vapor composition leaving this tray (VLE) y_out = self.vapor_liquid_equilibrium(x_tray) # Component material balance for light component # Accumulation = In - Out light_in = L_in * x_in + V_in * y_in light_out = L_out * x_tray + V_out * y_out dx_dt = (light_in - light_out) / self.holdup return np.array([dx_dt])
[docs] def steady_state(self, u: np.ndarray) -> np.ndarray: """ Calculate steady-state composition. For steady state: dx/dt = 0 Light_in = Light_out L_in * x_in + V_in * y_in = L_out * x + V_out * y Args: u: [L_in, x_in, V_in, y_in, L_out, V_out] Returns: [x_tray_ss] - steady-state liquid composition """ L_in, x_in, V_in, y_in, L_out, V_out = u # Solve: L_in * x_in + V_in * y_in = L_out * x + V_out * y # where y = alpha * x / (1 + (alpha - 1) * x) def objective(x): if x < 0 or x > 1: return 1e6 # Penalty for invalid compositions y = self.vapor_liquid_equilibrium(x) return abs(L_in * x_in + V_in * y_in - L_out * x - V_out * y) # Find solution using simple search from scipy.optimize import minimize_scalar result = minimize_scalar(objective, bounds=(0, 1), method='bounded') return np.array([result.x])