Spaces:
Sleeping
Sleeping
File size: 7,181 Bytes
38db72f 6f2bbe3 38db72f | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 | """
Fluid Bed Coating Digital Twin β Streamlit app (Plotly rendering variant).
Functionally identical to app.py, but charts are rendered with interactive
Plotly figures instead of Matplotlib. The simulation itself (constants,
correlations, PHβSPβDR chaining, dissolution model) is reused unchanged from the
fluid_bed package; only the rendering layer differs and lives in plotly_figures.py.
Run with: streamlit run app_plotly.py
"""
import sys
import os
# Make the fluid_bed package importable whether or not it is pip-installed.
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "src"))
import numpy as np
import streamlit as st
from fluid_bed.simulate import run_full_process
from fluid_bed.models.dissolution import dissolution_curve
from plotly_figures import make_process_figure, make_dissolution_figure
# ββ Core simulation (results cached per unique parameter set) βββββββββββββββββ
@st.cache_data(show_spinner="Running ODE simulationβ¦")
def run_simulation(
d_mm, ssa_cm2g, T0_C, batch_kg, humidity_g_kg, dmc_pct, coating_level,
ph_T_C, ph_flow, ph_dur_min,
sp_T_C, sp_flow, sp_rate_g_min,
dr_T_C, dr_flow, dr_dur_min,
):
return run_full_process(
d_mm, ssa_cm2g, T0_C, batch_kg, humidity_g_kg, dmc_pct, coating_level,
ph_T_C, ph_flow, ph_dur_min,
sp_T_C, sp_flow, sp_rate_g_min,
dr_T_C, dr_flow, dr_dur_min,
)
# ββ Dissolution panel (fragment: t_sample reruns only this section) βββββββββββ
@st.fragment
def dissolution_panel(sim, ssa_cm2g):
st.markdown("### Dissolution β Virtual Sample Withdrawal")
st.caption(
"Move the slider to any process time. "
"The dissolution curve updates instantly (simulation result is cached)."
)
t_max = float(sim.t_end)
if "t_sample" not in st.session_state:
st.session_state["t_sample"] = min(float(sim.sp_end), t_max)
elif st.session_state["t_sample"] > t_max:
st.session_state["t_sample"] = t_max
t_proc_min = st.slider(
"Sample time (min)",
min_value=0.0,
max_value=t_max,
step=0.5,
key="t_sample",
)
fig_diss, wg_at_t, wg_nl_at_t, k, stage_name, F_diss, t_diss = (
make_dissolution_figure(sim, t_proc_min, ssa_cm2g, dissolution_curve)
)
# theme=None β use the figure's own (white) template instead of Streamlit's
# dark theme, which would otherwise force title/tick fonts to white.
st.plotly_chart(fig_diss, use_container_width=True, theme=None)
# Dissolution metrics
t50 = float(np.interp(50, F_diss, t_diss)) if F_diss[-1] >= 50 else float("nan")
t80 = float(np.interp(80, F_diss, t_diss)) if F_diss[-1] >= 80 else float("nan")
d1, d2, d3, d4 = st.columns(4)
d1.metric("Stage", stage_name)
d2.metric("tβ
β (min)", f"{t50:.0f}" if not np.isnan(t50) else "> 240")
d3.metric("tββ (min)", f"{t80:.0f}" if not np.isnan(t80) else "> 240")
d4.metric("k (sβ»ΒΉ)", f"{k:.4e}")
# ββ Page layout ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
st.set_page_config(
page_title="Fluid Bed Coating Digital Twin (Plotly)",
page_icon="π§ͺ",
layout="wide",
)
st.title("Fluid Bed Coating Digital Twin")
st.caption(
"**Pre-heating β Spraying β Drying β Dissolution prediction.** "
"r_spray and r_dry are derived from the AICc-selected empirical correlations "
"(r_spray RΒ²=0.844, r_dry RΒ²=0.851; Bosch 2018 DoE, 19 runs)."
)
st.caption(
"**Full model development**: [GitHub repo](https://github.com/SamdGuizani/FB_Coating_Digital_Twin)"
)
# ββ Sidebar sliders βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
with st.sidebar:
st.markdown("## Parameters")
st.markdown("**Particle / Batch**")
d_mm = st.slider("Diameter (mm)", 0.80, 1.50, 1.00, 0.05)
ssa_cm2g = st.slider("SSA (cmΒ²/g)", 55.0, 75.0, 65.0, 1.0)
T0_C = st.slider("Tβ particle (Β°C)", 15.0, 30.0, 20.0, 1.0)
batch_kg = st.slider("Batch size (kg)", 3.0, 6.0, 4.6, 0.1)
humidity_g_kg = st.slider("Humidity (g/kg)", 0.0, 25.0, 13.4, 0.5)
dmc_pct = st.slider("DMC (% m/m)", 1.0, 2.0, 1.5, 0.1,
help="Coating solution dry-matter concentration. Feeds the "
"correlations as CC (coating concentration).")
coating_level = st.slider("Coating level", -1.0, 1.0, 0.5, 0.05,
help="Coded DoE coating level (β1β¦+1). Converted to the dry-matter "
"ratio DM = 1.7Β·level + 6.4 g/kg, which feeds the correlations.")
st.markdown("**Pre-heating**")
ph_T_C = st.slider("[PH] T inlet (Β°C)", 40.0, 60.0, 50.0, 2.0)
ph_flow = st.slider("[PH] Flow (mΒ³/h)", 200, 400, 250, 10)
ph_dur_min = st.slider("[PH] Duration (min)", 10.0, 30.0, 20.0, 1.0)
st.markdown("**Spraying** *(r_spray from correlation)*")
sp_T_C = st.slider("[SP] T inlet (Β°C)", 30.0, 60.0, 40.0, 2.0)
sp_flow = st.slider("[SP] Flow (mΒ³/h)", 200, 400, 250, 10)
sp_rate_g_min = st.slider("[SP] Spray rate (g/min)", 95, 150, 120, 5)
st.markdown("**Drying** *(r_dry from correlation)*")
dr_T_C = st.slider("[DR] T inlet (Β°C)", 30.0, 60.0, 40.0, 2.0)
dr_flow = st.slider("[DR] Flow (mΒ³/h)", 200, 400, 250, 10)
dr_dur_min = st.slider("[DR] Duration (min)", 3.0, 15.0, 9.0, 1.0)
# ββ Run simulation (result cached per unique slider combination) ββββββββββββββ
sim = run_simulation(
d_mm, ssa_cm2g, T0_C, batch_kg, humidity_g_kg, dmc_pct, coating_level,
ph_T_C, ph_flow, ph_dur_min,
sp_T_C, sp_flow, sp_rate_g_min,
dr_T_C, dr_flow, dr_dur_min,
)
# ββ Summary metrics row ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
c1, c2, c3, c4, c5 = st.columns(5)
c1.metric("r_spray (Γ10β»βΆ kg/s)", f"{sim.r_spraying*1e6:.2f}")
c2.metric("r_dry (Γ10β»Β³ kg/s)", f"{sim.r_drying*1e3:.3f}")
c3.metric("DM ratio (g/kg)", f"{sim.dm_ratio_g_kg:.2f}")
c4.metric("WG end-spray", f"{sim.wg_end_spray:.3f}%")
c5.metric("WG final", f"{sim.wg_final:.3f}%")
# ββ Process plots βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
fig_proc = make_process_figure(sim, ph_T_C, sp_T_C, dr_T_C)
st.plotly_chart(fig_proc, use_container_width=True, theme=None)
# ββ Dissolution panel βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
st.divider()
dissolution_panel(sim, ssa_cm2g)
|