Justify / main.py
3v324v23's picture
changed header link
5c1d275
Raw History Blame Contribute Delete
13.7 kB
from dash import Dash, Input, Output, State, no_update
from data.functions import *
from transportEqs import *
from Kps_function.read_Kps_model import *
import os
from figure import build_figure
from main_layout import build_main_layout
from instructions import build_instructions_layout
from rst_info import build_rst_layout
from dash import html, dcc
import base64
import io
app = Dash(
__name__,
assets_folder=os.path.join(os.path.dirname(__file__), "assets"),
suppress_callback_exceptions=True,
#external_stylesheets=[dbc.themes.BOOTSTRAP],
)
# --------------------------------------------------------------------
# Layout
# --------------------------------------------------------------------
app.layout = html.Div([
dcc.Location(id="url", refresh=False),
html.Header(
html.Div([
html.A(
html.Img(
src="/assets/gov-fda-new-white.svg",
alt="U.S. Food and Drug Administration",
className="fda-logo",
),
href="https://www.fda.gov",
target="_blank",
className="site-brand",
),
html.Span(
"Extraction condition justification tool",
className="site-tool-title",
),
html.Nav([
dcc.Link("Model", href="/"),
dcc.Link("Quick start", href="/instructions"),
dcc.Link("About this tool", href="/rst"),
], className="site-nav", **{"aria-label": "Main navigation"}),
], className="site-container site-header-inner"),
className="site-header",
),
html.Main(
html.Div(id="page-content"),
id="main-content",
className="site-container site-main",
),
html.Footer(
html.Div("Office of Science and Engineering Laboratories · Contact", className="site-container"),
className="site-footer",
),
])
app.validation_layout = html.Div([
app.layout,
build_main_layout(),
build_instructions_layout(),
build_rst_layout(),
])
@app.callback(
Output("page-content", "children"),
Input("url", "pathname")
)
def route(pathname):
if pathname == "/instructions":
return build_instructions_layout()
elif pathname == "/rst":
return build_rst_layout()
else:
return build_main_layout()
# --------------------------------------------------------------------
# Callbacks
# --------------------------------------------------------------------
@app.callback(
Output("solute_upload_container", "style"),
Input("solute_source", "value"),
)
def show_solute_upload(solute_source):
if solute_source == "upload":
return {"display": "block"}
return {"display": "none"}
@app.callback(
Output("uploaded_solute_data", "data"),
Output("solute_upload_status", "children"),
Input("solute_upload", "contents"),
State("solute_upload", "filename"),
prevent_initial_call=True,
)
def load_solute_file(contents, filename):
if contents is None:
return None, ""
try:
content_type, content_string = contents.split(",")
decoded = base64.b64decode(content_string)
uploaded_df = pd.read_excel(
io.BytesIO(decoded)
)
required_columns = [
"CASRN",
"MW",
"LogP",
#"Vabc",
]
missing = [
col for col in required_columns
if col not in uploaded_df.columns
]
if missing:
return (
None,
f"Missing required columns: {', '.join(missing)}"
)
numeric_columns = [
"MW",
"LogP",
#"Vabc",
]
for col in numeric_columns:
uploaded_df[col] = pd.to_numeric(
uploaded_df[col],
errors="coerce"
)
if uploaded_df[numeric_columns].isna().any().any():
return (
None,
"One or more required numeric values are missing or invalid."
)
return (
uploaded_df.to_dict("records"),
f"Loaded {len(uploaded_df)} solutes from {filename}"
)
except Exception as e:
return None, f"Could not read file: {e}"
@app.callback(
Output("c1_solvent", "disabled"),
Output("c1_volume", "disabled"),
Output("c1_swelling", "disabled"),
Output("c1_temp", "disabled"),
Output("c1_iterations", "disabled"),
Input("condition1_mode", "value"),
State("c1_solvent", "value"),
)
def toggle_condition1_inputs(mode, current_value):
disable = (mode != "in_vitro")
return disable, disable, disable, disable, disable
@app.callback(
Output("c2_solvent", "disabled"),
Output("c2_volume", "disabled"),
Output("c2_swelling", "disabled"),
Output("c2_temp", "disabled"),
Output("c2_iterations", "disabled"),
Input("condition2_mode", "value"),
State("c2_solvent", "value"),
)
def toggle_condition2_inputs(mode, current_value):
disable = (mode != "in_vitro")
return disable, disable, disable, disable, disable
@app.callback(
Output("mm0_chart", "figure"),
Output("solute_calculate_warning", "children"),
Input("calculate", "n_clicks"),
State("matrix", "value"),
State("tg", "value"),
State("crystal", "value"),
State("density", "value"),
State("polymer_volume", "value"),
State("surface_area", "value"),
# Condition 1
State("condition1_mode", "value"),
State("c1_solvent", "value"),
State("c1_volume", "value"),
State("c1_swelling", "value"),
State("c1_temp", "value"),
State("c1_time", "value"),
State("c1_iterations", "value"),
# Condition 2
State("condition2_mode", "value"),
State("c2_solvent", "value"),
State("c2_volume", "value"),
State("c2_swelling", "value"),
State("c2_temp", "value"),
State("c2_time", "value"),
State("c2_iterations", "value"),
# MC
State("samples", "value"),
State("mc_option", "value"),
# Solutes
State("solute_source", "value"),
State("uploaded_solute_data", "data"),
prevent_initial_call=True,
)
def update_chart(n_clicks,
matrix, tg, crystal, density, polymer_volume, surface_area,
c1_mode, c1_solvent, c1_volume, c1_swelling,
c1_temp, c1_time, c1_iter,
c2_mode, c2_solvent, c2_volume,
c2_swelling, c2_temp, c2_time, c2_iter,samples,mc_option,solute_source,uploaded_solute_data):
# ---------------------------------------------------------
# Validate solute source
# ---------------------------------------------------------
if solute_source == "default":
current_soluteData = soluteData.copy()
elif solute_source == "upload":
if not uploaded_solute_data:
return (
no_update,
"Please upload a valid Excel file before calculating."
)
current_soluteData = pd.DataFrame.from_records(
uploaded_solute_data
)
else:
return (
no_update,
"Please select either the default or user-defined chemical set."
)
df = pd.DataFrame()
df['CASRN'] = current_soluteData['CASRN'].to_numpy()
current_soluteData["LogP"] = current_soluteData["LogP"].clip(upper=10)
current_soluteData["Vabc"] = current_soluteData["MW"]
N = int(samples)
pindex = np.where(polymers == matrix)[0][0]
CHRIS_category = categories[pindex]
Polymer_Density = float(density)
Polymer_Tg = float(tg) + 273.15
Polymer_X = float(crystal)/100.
L = float(polymer_volume)/float(surface_area)
# Condition 1
ExtractionTime = float(c1_time) * 3600.
if c1_mode == 'in_vivo_conservative':
c1_dist = ConservativeMonteCarlo(current_soluteData, CHRIS_category, L, ExtractionTime, N)
elif c1_mode == 'in_vivo_tissue':
c1_dist = TissueMonteCarlo(current_soluteData, CHRIS_category, L, ExtractionTime, N)
else:
Solvent_Name = c1_solvent
Solvent_MW = Solvent_MWs[Solvent_Name]
Solvent_Density = Solvent_Densities[Solvent_Name]
Solvent_PI = Solvent_PIs[Solvent_Name]
ExtractionT = float(c1_temp) + 273.15
Ms_M0 = float(c1_swelling)
Swell = 1.+(Ms_M0-1.)*Polymer_Density/Solvent_Density
w = (Ms_M0 - 1.)/(Ms_M0-Polymer_X)
Iterations = float(c1_iter)
Lbath = float(c1_volume) / float(surface_area)
c1_dist = ExtractMonteCarlo(current_soluteData, Solvent_PI, w, ExtractionT, Polymer_Tg, Solvent_Name, Solvent_MW,
ExtractionTime, Swell, Iterations, CHRIS_category, L, Lbath, N)
# Condition 2
ExtractionTime = float(c2_time) * 3600.
if c2_mode == 'in_vivo_conservative':
c2_dist = ConservativeMonteCarlo(current_soluteData, CHRIS_category, L, ExtractionTime, N)
elif c2_mode == 'in_vivo_tissue':
c2_dist = TissueMonteCarlo(current_soluteData, CHRIS_category, L, ExtractionTime, N)
else:
Solvent_Name = c2_solvent
Solvent_MW = Solvent_MWs[Solvent_Name]
Solvent_Density = Solvent_Densities[Solvent_Name]
Solvent_PI = Solvent_PIs[Solvent_Name]
ExtractionT = float(c2_temp) + 273.15
Ms_M0 = float(c2_swelling)
Swell = 1. + (Ms_M0 - 1.) * Polymer_Density / Solvent_Density
w = (Ms_M0 - 1.) / (Ms_M0 - Polymer_X)
Iterations = float(c2_iter)
Lbath = float(c2_volume) / float(surface_area)
c2_dist = ExtractMonteCarlo(current_soluteData, Solvent_PI, w, ExtractionT, Polymer_Tg, Solvent_Name, Solvent_MW,
ExtractionTime, Swell, Iterations, CHRIS_category, L, Lbath, N)
if mc_option == "absolute":
c1_lower, c1_median, c1_upper = np.percentile(
c1_dist,
[25, 50, 75],
axis=1
)
c2_lower, c2_median, c2_upper = np.percentile(
c2_dist,
[25, 50, 75],
axis=1
)
df['Condition 1'] = c1_median
df['Cond1_err_plus'] = c1_upper - c1_median
df['Cond1_err_minus'] = c1_median - c1_lower
df['Condition 2'] = c2_median
df['Cond2_err_plus'] = c2_upper - c2_median
df['Cond2_err_minus'] = c2_median - c2_lower
elif mc_option == "ratio":
ratio_dist = np.divide(
c1_dist,
c2_dist,
out=np.full_like(c1_dist, np.nan),
where=c2_dist > 0
)
ratio_lower, ratio_median, ratio_upper = np.percentile(
ratio_dist,
[25, 50, 75],
axis=1
)
df['Ratio'] = ratio_median
df['Ratio_err_plus'] = ratio_upper - ratio_median
df['Ratio_err_minus'] = ratio_median - ratio_lower
fig = build_figure(df, mc_option)
return fig, ""
def ConservativeMonteCarlo(soluteData, CHRIS_category, L, time, N):
nSolutes = len(soluteData)
distributions = zeros((nSolutes, N))
for iSolute in range(nSolutes):
Solute_MW = soluteData['MW'].iloc[iSolute]
D_CHRIS = get_D_CHRIS(
Solute_MW,
CHRIS_category,
N=N
)
tau = D_CHRIS * time / L**2
mass = Conservative(tau)
distributions[iSolute, :] = mass
return distributions
def TissueMonteCarlo(soluteData, CHRIS_category, L, time, N):
nSolutes = len(soluteData)
distributions = zeros((nSolutes, N))
for iSolute in range(nSolutes):
Solute_MW = soluteData['MW'].iloc[iSolute]
Solute_logP = soluteData['LogP'].iloc[iSolute]
D_CHRIS = get_D_CHRIS(
Solute_MW,
CHRIS_category,
N=N
)
tau = D_CHRIS * time / L**2
Dt = get_Dt(N)
Kpt = get_Kpt(Solute_logP, N)
beta = (1. / Kpt) * sqrt(Dt / D_CHRIS)
mass = SolubilityLimited(beta, tau)
distributions[iSolute, :] = mass
return distributions
def ExtractMonteCarlo(
soluteData, Solvent_PI, w, ExtractionT,
Polymer_Tg, Solvent_Name, Solvent_MW,
ExtractionTime, Swell, Iterations,
CHRIS_category, L, Lbath, N):
nSolutes = len(soluteData)
distributions = zeros((nSolutes, N))
for iSolute in range(nSolutes):
Solute_MW = soluteData['MW'].iloc[iSolute]
Solute_logP = soluteData['LogP'].iloc[iSolute]
Solute_Vabc = soluteData['Vabc'].iloc[iSolute]
if Solvent_Name == 'blood':
Kps = get_Kpb(Solute_logP, N)
else:
ToKPS = array([[Solute_logP, Solvent_PI]])
model = QuantileGridFromCoeffs(
export_dir='Kps_function/Kps_model'
)
Kps = 10 ** model.sample(
ToKPS,
n_samples=N
)[0]
D_Extract = get_D_Extract(
w,
ExtractionT,
Polymer_Tg,
Solvent_Name,
Solvent_MW,
Solute_MW,
Solute_Vabc,
CHRIS_category,
N=N
)
tau = D_Extract * ExtractionTime / L**2
alpha = Lbath / L / Kps
massExtraction = zeros(N)
for i in range(N):
massExtraction[i] = Extraction(
tau[i],
alpha[i],
Kps[i],
Swell,
Iterations
)
distributions[iSolute, :] = massExtraction
return distributions
if __name__ == "__main__":
app.run(debug=True)