dzs commited on
Commit
5ae2332
·
1 Parent(s): b444bf3

added user defined solute functionality

Browse files
Files changed (4) hide show
  1. data/soluteData.xlsx +2 -2
  2. instructions.py +120 -3
  3. main.py +112 -17
  4. main_layout.py +57 -0
data/soluteData.xlsx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:4fcdcf105050265e26b8a02ea38ae6ce9a8d1618f9a29fd0282579b026fc4478
3
- size 9612
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7fda2bcddef252ad66a0529ca9e1f600f0ff2527cfaca8655e7d674d4e7e9e5d
3
+ size 9715
instructions.py CHANGED
@@ -41,6 +41,19 @@ def build_instructions_layout() -> html.Div:
41
  "fontSize": "13.5px",
42
  }
43
 
 
 
 
 
 
 
 
 
 
 
 
 
 
44
  return html.Div(
45
  style=page_style,
46
  children=[
@@ -112,11 +125,115 @@ def build_instructions_layout() -> html.Div:
112
  ],
113
  ),
114
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
115
  # ---------------------------------------------------------
116
  # 2. Conditions
117
  # ---------------------------------------------------------
118
  html.H2(
119
- "2) Define Condition #1 and Condition #2",
120
  style=h2_style,
121
  ),
122
 
@@ -185,7 +302,7 @@ def build_instructions_layout() -> html.Div:
185
  # ---------------------------------------------------------
186
  # 3. Monte Carlo
187
  # ---------------------------------------------------------
188
- html.H2("3) Monte Carlo settings", style=h2_style),
189
 
190
  html.P(
191
  "Set the number of samples under “Monte Carlo → Samples” and "
@@ -238,7 +355,7 @@ def build_instructions_layout() -> html.Div:
238
  # ---------------------------------------------------------
239
  # 4. Results
240
  # ---------------------------------------------------------
241
- html.H2("4) Run and interpret results", style=h2_style),
242
 
243
  html.P(
244
  [
 
41
  "fontSize": "13.5px",
42
  }
43
 
44
+ table_header_style = {
45
+ "border": "1px solid #ccc",
46
+ "padding": "5px 8px",
47
+ "textAlign": "left",
48
+ "backgroundColor": "#f2f2f2",
49
+ }
50
+
51
+ table_cell_style = {
52
+ "border": "1px solid #ccc",
53
+ "padding": "5px 8px",
54
+ "textAlign": "left",
55
+ }
56
+
57
  return html.Div(
58
  style=page_style,
59
  children=[
 
125
  ],
126
  ),
127
 
128
+ # ---------------------------------------------------------
129
+ # 2. Solutes
130
+ # ---------------------------------------------------------
131
+ html.H2("2) Choose solutes", style=h2_style),
132
+
133
+ html.P(
134
+ "Select the set of chemicals to evaluate in the “Solutes” section. "
135
+ "You can use the default representative boundary chemicals or upload "
136
+ "a user-defined Excel file.",
137
+ style=p_style,
138
+ ),
139
+
140
+ html.Ul(
141
+ style=ul_style,
142
+ children=[
143
+ html.Li(
144
+ [
145
+ html.B("Default boundary chemicals: "),
146
+ "Uses the predefined set of representative chemicals included "
147
+ "with the tool.",
148
+ ],
149
+ style=li_style,
150
+ ),
151
+
152
+ html.Li(
153
+ [
154
+ html.B("User-defined chemicals: "),
155
+ "Upload an Excel file containing the chemicals you want to evaluate.",
156
+ ],
157
+ style=li_style,
158
+ ),
159
+ ],
160
+ ),
161
+
162
+ html.Div(
163
+ style=callout_style,
164
+ children=[
165
+ html.B("Required Excel columns"),
166
+ html.Br(),
167
+ "A user-defined Excel file must contain the following four columns, "
168
+ "with the column names written exactly as shown:",
169
+ html.Br(),
170
+ html.Br(),
171
+
172
+ html.Table(
173
+ [
174
+ html.Thead(
175
+ html.Tr(
176
+ [
177
+ html.Th("CASRN", style=table_header_style),
178
+ html.Th("MW", style=table_header_style),
179
+ html.Th("LogP", style=table_header_style),
180
+ html.Th("Vabc", style=table_header_style),
181
+ ]
182
+ )
183
+ ),
184
+
185
+ html.Tbody(
186
+ [
187
+ html.Tr(
188
+ [
189
+ html.Td(
190
+ "Chemical Abstracts Service Registry Number",
191
+ style=table_cell_style,
192
+ ),
193
+ html.Td(
194
+ "Molecular weight",
195
+ style=table_cell_style,
196
+ ),
197
+ html.Td(
198
+ "Octanol/water partition coefficient (log10)",
199
+ style=table_cell_style,
200
+ ),
201
+ html.Td(
202
+ "Molecular volume",
203
+ style=table_cell_style,
204
+ ),
205
+ ]
206
+ )
207
+ ]
208
+ ),
209
+ ],
210
+ style={
211
+ "borderCollapse": "collapse",
212
+ "width": "100%",
213
+ "marginTop": "6px",
214
+ "marginBottom": "6px",
215
+ },
216
+ ),
217
+
218
+ html.Br(),
219
+
220
+ "The MW, LogP, and Vabc columns must contain numeric values. "
221
+ "LogP values greater than 10 are limited to 10 for use in the model.",
222
+ ],
223
+ ),
224
+
225
+ html.P(
226
+ "After a valid file is uploaded, the tool will use the uploaded chemical "
227
+ "properties in place of the default boundary-chemical set when Calculate "
228
+ "is selected.",
229
+ style=small_style,
230
+ ),
231
+
232
  # ---------------------------------------------------------
233
  # 2. Conditions
234
  # ---------------------------------------------------------
235
  html.H2(
236
+ "3) Define Condition #1 and Condition #2",
237
  style=h2_style,
238
  ),
239
 
 
302
  # ---------------------------------------------------------
303
  # 3. Monte Carlo
304
  # ---------------------------------------------------------
305
+ html.H2("4) Monte Carlo settings", style=h2_style),
306
 
307
  html.P(
308
  "Set the number of samples under “Monte Carlo → Samples” and "
 
355
  # ---------------------------------------------------------
356
  # 4. Results
357
  # ---------------------------------------------------------
358
+ html.H2("5) Run and interpret results", style=h2_style),
359
 
360
  html.P(
361
  [
main.py CHANGED
@@ -11,6 +11,8 @@ from main_layout import build_main_layout
11
  from instructions import build_instructions_layout
12
  from rst_info import build_rst_layout
13
  from dash import html, dcc
 
 
14
 
15
  app = Dash(
16
  __name__,
@@ -47,8 +49,85 @@ def route(pathname):
47
  return build_main_layout()
48
 
49
  # --------------------------------------------------------------------
50
- # Main callback
51
  # --------------------------------------------------------------------
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
52
  @app.callback(
53
  Output("c1_solvent", "disabled"),
54
  Output("c1_volume", "disabled"),
@@ -86,6 +165,7 @@ def toggle_condition2_inputs(mode, current_value):
86
  State("density", "value"),
87
  State("polymer_volume", "value"),
88
  State("surface_area", "value"),
 
89
  # Condition 1
90
  State("condition1_mode", "value"),
91
  State("c1_solvent", "value"),
@@ -105,6 +185,9 @@ def toggle_condition2_inputs(mode, current_value):
105
  # MC
106
  State("samples", "value"),
107
  State("mc_option", "value"),
 
 
 
108
  prevent_initial_call=True,
109
  )
110
 
@@ -113,13 +196,25 @@ def update_chart(n_clicks,
113
  c1_mode, c1_solvent, c1_volume, c1_swelling,
114
  c1_temp, c1_time, c1_iter,
115
  c2_mode, c2_solvent, c2_volume,
116
- c2_swelling, c2_temp, c2_time, c2_iter,samples,mc_option):
 
 
 
 
 
 
 
 
 
117
 
118
- # TODO: replace with logic that uses the form inputs
119
- # For now, simply return the same figure when "Calculate" is clicked.
 
120
 
121
  df = pd.DataFrame()
122
- df['CASRN'] = soluteData['CASRN'].to_numpy()
 
 
123
 
124
  N = int(samples)
125
  pindex = np.where(polymers == matrix)[0][0]
@@ -133,10 +228,10 @@ def update_chart(n_clicks,
133
  # Condition 1
134
  ExtractionTime = float(c1_time) * 3600.
135
  if c1_mode == 'in_vivo_conservative':
136
- c1_dist = ConservativeMonteCarlo(soluteData, CHRIS_category, L, ExtractionTime, N)
137
 
138
  elif c1_mode == 'in_vivo_tissue':
139
- c1_dist = TissueMonteCarlo(soluteData, CHRIS_category, L, ExtractionTime, N)
140
 
141
  else:
142
  Solvent_Name = c1_solvent
@@ -150,16 +245,16 @@ def update_chart(n_clicks,
150
  Iterations = float(c1_iter)
151
  Lbath = float(c1_volume) / float(surface_area)
152
 
153
- c1_dist = ExtractMonteCarlo(soluteData, Solvent_PI, w, ExtractionT, Polymer_Tg, Solvent_Name, Solvent_MW,
154
  ExtractionTime, Swell, Iterations, CHRIS_category, L, Lbath, N)
155
 
156
  # Condition 2
157
  ExtractionTime = float(c2_time) * 3600.
158
  if c2_mode == 'in_vivo_conservative':
159
- c2_dist = ConservativeMonteCarlo(soluteData, CHRIS_category, L, ExtractionTime, N)
160
 
161
  elif c2_mode == 'in_vivo_tissue':
162
- c2_dist = TissueMonteCarlo(soluteData, CHRIS_category, L, ExtractionTime, N)
163
 
164
  else:
165
  Solvent_Name = c2_solvent
@@ -173,7 +268,7 @@ def update_chart(n_clicks,
173
  Iterations = float(c2_iter)
174
  Lbath = float(c2_volume) / float(surface_area)
175
 
176
- c2_dist = ExtractMonteCarlo(soluteData, Solvent_PI, w, ExtractionT, Polymer_Tg, Solvent_Name, Solvent_MW,
177
  ExtractionTime, Swell, Iterations, CHRIS_category, L, Lbath, N)
178
 
179
  if mc_option == "absolute":
@@ -227,7 +322,7 @@ def ConservativeMonteCarlo(soluteData, CHRIS_category, L, time, N):
227
 
228
  for iSolute in range(nSolutes):
229
 
230
- Solute_MW = soluteData['MW_new'].iloc[iSolute]
231
 
232
  D_CHRIS = get_D_CHRIS(
233
  Solute_MW,
@@ -249,8 +344,8 @@ def TissueMonteCarlo(soluteData, CHRIS_category, L, time, N):
249
 
250
  for iSolute in range(nSolutes):
251
 
252
- Solute_MW = soluteData['MW_new'].iloc[iSolute]
253
- Solute_logP = soluteData['LogP_new'].iloc[iSolute]
254
 
255
  D_CHRIS = get_D_CHRIS(
256
  Solute_MW,
@@ -282,8 +377,8 @@ def ExtractMonteCarlo(
282
 
283
  for iSolute in range(nSolutes):
284
 
285
- Solute_MW = soluteData['MW_new'].iloc[iSolute]
286
- Solute_logP = soluteData['LogP_new'].iloc[iSolute]
287
  Solute_Vabc = soluteData['Vabc'].iloc[iSolute]
288
 
289
  if Solvent_Name == 'blood':
@@ -334,4 +429,4 @@ def ExtractMonteCarlo(
334
 
335
 
336
  if __name__ == "__main__":
337
- app.run(debug=False)
 
11
  from instructions import build_instructions_layout
12
  from rst_info import build_rst_layout
13
  from dash import html, dcc
14
+ import base64
15
+ import io
16
 
17
  app = Dash(
18
  __name__,
 
49
  return build_main_layout()
50
 
51
  # --------------------------------------------------------------------
52
+ # Callbacks
53
  # --------------------------------------------------------------------
54
+
55
+ @app.callback(
56
+ Output("solute_upload_container", "style"),
57
+ Input("solute_source", "value"),
58
+ )
59
+ def show_solute_upload(solute_source):
60
+
61
+ if solute_source == "upload":
62
+ return {"display": "block"}
63
+
64
+ return {"display": "none"}
65
+
66
+ @app.callback(
67
+ Output("uploaded_solute_data", "data"),
68
+ Output("solute_upload_status", "children"),
69
+ Input("solute_upload", "contents"),
70
+ State("solute_upload", "filename"),
71
+ prevent_initial_call=True,
72
+ )
73
+ def load_solute_file(contents, filename):
74
+
75
+ if contents is None:
76
+ return None, ""
77
+
78
+ try:
79
+ content_type, content_string = contents.split(",")
80
+
81
+ decoded = base64.b64decode(content_string)
82
+
83
+ uploaded_df = pd.read_excel(
84
+ io.BytesIO(decoded)
85
+ )
86
+
87
+ required_columns = [
88
+ "CASRN",
89
+ "MW",
90
+ "LogP",
91
+ "Vabc",
92
+ ]
93
+
94
+ missing = [
95
+ col for col in required_columns
96
+ if col not in uploaded_df.columns
97
+ ]
98
+
99
+ if missing:
100
+ return (
101
+ None,
102
+ f"Missing required columns: {', '.join(missing)}"
103
+ )
104
+
105
+ numeric_columns = [
106
+ "MW",
107
+ "LogP",
108
+ "Vabc",
109
+ ]
110
+
111
+ for col in numeric_columns:
112
+ uploaded_df[col] = pd.to_numeric(
113
+ uploaded_df[col],
114
+ errors="coerce"
115
+ )
116
+
117
+ if uploaded_df[numeric_columns].isna().any().any():
118
+ return (
119
+ None,
120
+ "One or more required numeric values are missing or invalid."
121
+ )
122
+
123
+ return (
124
+ uploaded_df.to_dict("records"),
125
+ f"Loaded {len(uploaded_df)} solutes from {filename}"
126
+ )
127
+
128
+ except Exception as e:
129
+ return None, f"Could not read file: {e}"
130
+
131
  @app.callback(
132
  Output("c1_solvent", "disabled"),
133
  Output("c1_volume", "disabled"),
 
165
  State("density", "value"),
166
  State("polymer_volume", "value"),
167
  State("surface_area", "value"),
168
+
169
  # Condition 1
170
  State("condition1_mode", "value"),
171
  State("c1_solvent", "value"),
 
185
  # MC
186
  State("samples", "value"),
187
  State("mc_option", "value"),
188
+ # Solutes
189
+ State("solute_source", "value"),
190
+ State("uploaded_solute_data", "data"),
191
  prevent_initial_call=True,
192
  )
193
 
 
196
  c1_mode, c1_solvent, c1_volume, c1_swelling,
197
  c1_temp, c1_time, c1_iter,
198
  c2_mode, c2_solvent, c2_volume,
199
+ c2_swelling, c2_temp, c2_time, c2_iter,samples,mc_option,solute_source,uploaded_solute_data):
200
+
201
+ if solute_source == "default":
202
+
203
+ current_soluteData = soluteData.copy()
204
+
205
+ else:
206
+
207
+ if uploaded_solute_data is None:
208
+ raise PreventUpdate
209
 
210
+ current_soluteData = pd.DataFrame(
211
+ uploaded_solute_data
212
+ )
213
 
214
  df = pd.DataFrame()
215
+ df['CASRN'] = current_soluteData['CASRN'].to_numpy()
216
+
217
+ current_soluteData["LogP"] = current_soluteData["LogP"].clip(upper=10)
218
 
219
  N = int(samples)
220
  pindex = np.where(polymers == matrix)[0][0]
 
228
  # Condition 1
229
  ExtractionTime = float(c1_time) * 3600.
230
  if c1_mode == 'in_vivo_conservative':
231
+ c1_dist = ConservativeMonteCarlo(current_soluteData, CHRIS_category, L, ExtractionTime, N)
232
 
233
  elif c1_mode == 'in_vivo_tissue':
234
+ c1_dist = TissueMonteCarlo(current_soluteData, CHRIS_category, L, ExtractionTime, N)
235
 
236
  else:
237
  Solvent_Name = c1_solvent
 
245
  Iterations = float(c1_iter)
246
  Lbath = float(c1_volume) / float(surface_area)
247
 
248
+ c1_dist = ExtractMonteCarlo(current_soluteData, Solvent_PI, w, ExtractionT, Polymer_Tg, Solvent_Name, Solvent_MW,
249
  ExtractionTime, Swell, Iterations, CHRIS_category, L, Lbath, N)
250
 
251
  # Condition 2
252
  ExtractionTime = float(c2_time) * 3600.
253
  if c2_mode == 'in_vivo_conservative':
254
+ c2_dist = ConservativeMonteCarlo(current_soluteData, CHRIS_category, L, ExtractionTime, N)
255
 
256
  elif c2_mode == 'in_vivo_tissue':
257
+ c2_dist = TissueMonteCarlo(current_soluteData, CHRIS_category, L, ExtractionTime, N)
258
 
259
  else:
260
  Solvent_Name = c2_solvent
 
268
  Iterations = float(c2_iter)
269
  Lbath = float(c2_volume) / float(surface_area)
270
 
271
+ c2_dist = ExtractMonteCarlo(current_soluteData, Solvent_PI, w, ExtractionT, Polymer_Tg, Solvent_Name, Solvent_MW,
272
  ExtractionTime, Swell, Iterations, CHRIS_category, L, Lbath, N)
273
 
274
  if mc_option == "absolute":
 
322
 
323
  for iSolute in range(nSolutes):
324
 
325
+ Solute_MW = soluteData['MW'].iloc[iSolute]
326
 
327
  D_CHRIS = get_D_CHRIS(
328
  Solute_MW,
 
344
 
345
  for iSolute in range(nSolutes):
346
 
347
+ Solute_MW = soluteData['MW'].iloc[iSolute]
348
+ Solute_logP = soluteData['LogP'].iloc[iSolute]
349
 
350
  D_CHRIS = get_D_CHRIS(
351
  Solute_MW,
 
377
 
378
  for iSolute in range(nSolutes):
379
 
380
+ Solute_MW = soluteData['MW'].iloc[iSolute]
381
+ Solute_logP = soluteData['LogP'].iloc[iSolute]
382
  Solute_Vabc = soluteData['Vabc'].iloc[iSolute]
383
 
384
  if Solvent_Name == 'blood':
 
429
 
430
 
431
  if __name__ == "__main__":
432
+ app.run(debug=True)
main_layout.py CHANGED
@@ -145,6 +145,63 @@ def build_main_layout() -> html.Div:
145
  ],
146
  ),
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  # Conditions row
149
  html.Div(
150
  style={
 
145
  ],
146
  ),
147
 
148
+ # Solutes row
149
+ html.H3(
150
+ "Solutes",
151
+ style={
152
+ "textDecoration": "underline",
153
+ "fontSize": "18px",
154
+ "marginBottom": "10px",
155
+ },
156
+ ),
157
+
158
+ dcc.RadioItems(
159
+ id="solute_source",
160
+ options=[
161
+ {"label": "Default boundary chemicals", "value": "default"},
162
+ {"label": "User-defined chemicals", "value": "upload"},
163
+ ],
164
+ value="default",
165
+ inline=True,
166
+ labelStyle={"marginRight": "15px"},
167
+ ),
168
+
169
+ html.Div(
170
+ id="solute_upload_container",
171
+ children=[
172
+ dcc.Upload(
173
+ id="solute_upload",
174
+ children=html.Div([
175
+ "Drag and drop or ",
176
+ html.A("select an Excel file"),
177
+ ]),
178
+ accept=".xlsx,.xls",
179
+ multiple=False,
180
+ style={
181
+ "width": "100%",
182
+ "height": "55px",
183
+ "lineHeight": "55px",
184
+ "borderWidth": "1px",
185
+ "borderStyle": "dashed",
186
+ "borderRadius": "4px",
187
+ "textAlign": "center",
188
+ "marginTop": "10px",
189
+ },
190
+ ),
191
+
192
+ html.Div(
193
+ id="solute_upload_status",
194
+ style={
195
+ "marginTop": "5px",
196
+ "fontSize": "13px",
197
+ },
198
+ ),
199
+ ],
200
+ style={"display": "none"},
201
+ ),
202
+
203
+ dcc.Store(id="uploaded_solute_data"),
204
+
205
  # Conditions row
206
  html.Div(
207
  style={