SamdGuizani
Trim Space to app-required files only
52af849
Raw
History Blame Contribute Delete
7.42 kB
"""
Full-process orchestration: pre-heating β†’ spraying β†’ drying.
`run_full_process` is the single implementation of the digital-twin chain that
both front-ends (Streamlit app.py and notebook 05b) consume. It takes operating
parameters in the units the UIs expose (mm, Β°C, mΒ³/h, g/min, %), converts to SI,
derives the coating-loss rates from the DoE-fitted empirical correlations, runs
the three stage ODEs with end-state chaining, and returns per-stage results
plus the concatenated time series ready for plotting (time in min, temperatures
in Β°C, solvent contents in wt %).
"""
from __future__ import annotations
from dataclasses import dataclass
import numpy as np
from .config import default_coater_params, wg_max_noloss
from .models.preheating import run_preheating, PreheatingResult
from .models.spraying import run_spraying, SprayingResult
from .models.drying_stage import run_drying, DryingResult
from .models.coating_correlations import calc_r_spraying, calc_r_drying_empirical
@dataclass
class FullProcessResult:
"""Per-stage ODE results plus plot-ready concatenated series."""
# Stage results (SI units, as returned by the stage solvers)
preheating: PreheatingResult
spraying: SprayingResult
drying: DryingResult
# Concatenated series over the whole process (plot units)
t_all: np.ndarray # min
T_product: np.ndarray # Β°C
T_gas: np.ndarray # Β°C (quasi-steady)
Y_particle: np.ndarray # wt % acetone on particles
Y_gas: np.ndarray # wt % acetone in gas
WG: np.ndarray # % coating weight gain
WG_noloss: np.ndarray # % theoretical no-loss reference
# Stage boundaries [min]
ph_end: float
sp_end: float
t_end: float
# Derived scalars
r_spraying: float # kg/s, from empirical correlation
r_drying: float # kg/s, from empirical correlation
dm_ratio_g_kg: float # g dry coating / kg particles
qty_sol_kg: float # kg coating solution sprayed
sp_dur_s: float # s, spraying duration
wg_max_noloss: float # %, theoretical maximum WG
wg_end_spray: float # %, WG at end of spraying
wg_final: float # %, WG at discharge
def run_full_process(
d_mm: float, ssa_cm2g: float, T0_C: float, batch_kg: float,
humidity_g_kg: float, dmc_pct: float, coating_level: float,
ph_T_C: float, ph_flow_m3h: float, ph_dur_min: float,
sp_T_C: float, sp_flow_m3h: float, sp_rate_g_min: float,
dr_T_C: float, dr_flow_m3h: float, dr_dur_min: float,
) -> FullProcessResult:
"""
Run the full PH β†’ SP β†’ DR chain from UI-unit operating parameters.
Parameters
----------
d_mm : equivalent particle diameter [mm]
ssa_cm2g : particle specific surface area [cmΒ²/g]
T0_C : initial particle temperature [Β°C]
batch_kg : batch mass [kg]
humidity_g_kg : measured inlet air absolute humidity [g/kg dry air]
(enters only the r_drying correlation, not the ODEs)
dmc_pct : coating solution dry-matter concentration [wt %]
coating_level : coded DoE coating-level factor [-1 … 1]; sets the
solution quantity via the DoE recipe
qty_sol = (0.0017Β·level + 0.0064)Β·batch/dmc_frac
ph/sp/dr_T_C : stage inlet air temperatures [Β°C]
ph/sp/dr_flow_m3h : stage air flows [mΒ³/h]
ph_dur_min, dr_dur_min : stage durations [min]
sp_rate_g_min : spray rate [g solution/min]; spraying duration follows
from qty_sol / spray rate
"""
dmc_frac = dmc_pct / 100.0
qty_sol_kg = (0.0017 * coating_level + 0.0064) * batch_kg / dmc_frac
sp_rate_kgs = sp_rate_g_min / 60_000.0
sp_dur_s = qty_sol_kg / sp_rate_kgs
dm_ratio_g_kg = qty_sol_kg * dmc_frac / batch_kg * 1000.0
rho_air = default_coater_params().rho_air
ph_m = ph_flow_m3h / 3600.0 * rho_air
sp_m = sp_flow_m3h / 3600.0 * rho_air
dr_m = dr_flow_m3h / 3600.0 * rho_air
ph_K, sp_K, dr_K = ph_T_C + 273.15, sp_T_C + 273.15, dr_T_C + 273.15
# Empirical correlations for coating loss rates
r_spray = calc_r_spraying(sp_rate_g_min, dmc_pct, dm_ratio_g_kg)
r_dry = calc_r_drying_empirical(batch_kg, dm_ratio_g_kg, ssa_cm2g, humidity_g_kg)
phys = dict(diameter_eq=d_mm * 1e-3, ssa_cm2_g=ssa_cm2g, batch_size=batch_kg)
# Inlet air carries no acetone; humidity enters only via r_dry above.
p_ph = default_coater_params(
**phys, air_flow_rates=(ph_m,) * 3,
air_temperatures=(ph_K,) * 3, air_inlet_moisture=(0.0, 0.0, 0.0))
res_ph = run_preheating(p_ph, duration=ph_dur_min * 60.0,
T_particle_init=T0_C + 273.15)
p_sp = default_coater_params(
**phys, air_flow_rates=(sp_m,) * 3,
air_temperatures=(sp_K,) * 3, air_inlet_moisture=(0.0, 0.0, 0.0),
spray_rate=sp_rate_kgs, dry_matter_conc=dmc_frac, r_spraying=r_spray)
res_sp = run_spraying(p_sp, duration=sp_dur_s,
T_particle_init=res_ph.T_particle[-1])
p_dr = default_coater_params(
**phys, air_flow_rates=(dr_m,) * 3,
air_temperatures=(dr_K,) * 3, air_inlet_moisture=(0.0, 0.0, 0.0),
r_drying=r_dry)
res_dr = run_drying(
p_dr, duration=dr_dur_min * 60.0,
Y_particle_init=res_sp.Y_particle[-1],
Y_gas_init=res_sp.Y_gas[-1],
M_coating_init=res_sp.M_coating[-1],
T_particle_init=res_sp.T_particle[-1],
)
# ── Concatenated plot series ──────────────────────────────────────────────
t_ph = res_ph.t / 60.0
t_sp = (res_sp.t + res_ph.t[-1]) / 60.0
t_dr = (res_dr.t + res_ph.t[-1] + res_sp.t[-1]) / 60.0
t_all = np.concatenate([t_ph, t_sp, t_dr])
wg_max = wg_max_noloss(qty_sol_kg, dmc_frac, batch_kg)
# No-loss WG reference: linear ramp during spray, flat plateau during dry
WG_noloss = np.concatenate([
np.zeros_like(t_ph),
np.linspace(0.0, wg_max, len(t_sp)),
np.full(len(t_dr), wg_max),
])
WG = np.concatenate([
np.zeros_like(t_ph),
res_sp.M_coating / batch_kg * 100,
res_dr.M_coating / batch_kg * 100,
])
T_prod = np.concatenate([
res_ph.T_particle - 273.15,
res_sp.T_particle - 273.15,
res_dr.T_particle - 273.15,
])
T_gas = np.concatenate([
res_ph.T_gas - 273.15,
res_sp.T_gas - 273.15,
res_dr.T_gas - 273.15,
])
Y_part = np.concatenate([
np.zeros_like(t_ph),
res_sp.Y_particle * 100,
res_dr.Y_particle * 100,
])
Y_gas_ = np.concatenate([
np.zeros_like(t_ph),
res_sp.Y_gas * 100,
res_dr.Y_gas * 100,
])
return FullProcessResult(
preheating=res_ph, spraying=res_sp, drying=res_dr,
t_all=t_all, T_product=T_prod, T_gas=T_gas,
Y_particle=Y_part, Y_gas=Y_gas_, WG=WG, WG_noloss=WG_noloss,
ph_end=float(t_ph[-1]), sp_end=float(t_sp[-1]), t_end=float(t_dr[-1]),
r_spraying=r_spray, r_drying=r_dry,
dm_ratio_g_kg=dm_ratio_g_kg, qty_sol_kg=qty_sol_kg, sp_dur_s=sp_dur_s,
wg_max_noloss=wg_max,
wg_end_spray=float(res_sp.M_coating[-1] / batch_kg * 100),
wg_final=float(res_dr.M_coating[-1] / batch_kg * 100),
)