Robert Elder commited on
Commit
abe0ff4
·
1 Parent(s): eccd0e1

updates to quantity module

Browse files
functions.py CHANGED
@@ -13,6 +13,12 @@ def SigFigs(number, n):
13
  if number == 0: return number
14
  return round(number, n - int(math.floor(math.log10(math.fabs(number)))) - 1)
15
 
 
 
 
 
 
 
16
  def Piringer(Mw, Ap, T=310.):
17
  # Semi-empirical model for D(Mw) given polymer property Ap- Toxicol. Sci. 2019, 172 (1), 201–212.
18
  if Mw > 1100.: # if molecule is greater than 1100 g/mol, default to that value as worst case
@@ -85,6 +91,8 @@ def CdfPlot(vals, units=None):
85
  xlabel = 'Total quantity' if units is None else f'Total quantity ({units})'
86
  fig, ax = plt.subplots(figsize=(6, 4))
87
  ax.ecdf(vals, c='b', lw=3)
 
 
88
  ax.set_xscale('log')
89
  ax.set(
90
  xlabel=xlabel,
 
13
  if number == 0: return number
14
  return round(number, n - int(math.floor(math.log10(math.fabs(number)))) - 1)
15
 
16
+ def HtmlNumber(number,n):
17
+ number_str = f'{number:.{n}e}'
18
+ base, exponent = number_str.split('e')
19
+ html_output = f'{base} × 10<sup>{exponent[0] if exponent[0]=="-" else ""}{int(exponent[1:]):d}</sup>'
20
+ return html_output
21
+
22
  def Piringer(Mw, Ap, T=310.):
23
  # Semi-empirical model for D(Mw) given polymer property Ap- Toxicol. Sci. 2019, 172 (1), 201–212.
24
  if Mw > 1100.: # if molecule is greater than 1100 g/mol, default to that value as worst case
 
91
  xlabel = 'Total quantity' if units is None else f'Total quantity ({units})'
92
  fig, ax = plt.subplots(figsize=(6, 4))
93
  ax.ecdf(vals, c='b', lw=3)
94
+ q50 = np.nanquantile(vals,0.5)
95
+ ax.plot([q50,q50],[0,0.5],'k--',[q50],[0.5],'ko',[ax.get_xlim()[0],q50],[0.5,0.5],'k--',lw=2,ms=10)
96
  ax.set_xscale('log')
97
  ax.set(
98
  xlabel=xlabel,
quantity_functions.py CHANGED
@@ -18,8 +18,8 @@ from functions import PowerLaw, Piringer
18
  ### TODO arbitrary T
19
 
20
  ## sampling parameters
21
- N_sample = int(1e5)
22
- rng = np.random.Generator(np.random.PCG64(seed=12345))
23
 
24
  ## list of solvents to include
25
  solvents = sorted(['ethanol', 'isopropanol', 'acetonitrile', 'toluene', 'dichloromethane', 'hexane'])
@@ -28,6 +28,9 @@ solvents = sorted(['ethanol', 'isopropanol', 'acetonitrile', 'toluene', 'dichlor
28
  T_cut = 20
29
  MW_cut = 20
30
 
 
 
 
31
  #### read data files
32
  # CHRIS bounds
33
  #with open('quantity_module/data/bounds.pkl', 'rb') as fp:
@@ -35,11 +38,15 @@ MW_cut = 20
35
  #with open('quantity_module/data/bounds-50.pkl', 'rb') as fp:
36
  # params_dict_ub_50, params_dict_lb_50, params_dict_ub_band_50, params_dict_lb_band_50, params_dict_q95_50, params_dict_q50_50, params_dict_q05_50 = pickle.load(fp)
37
  # CHRIS parameter distributions
38
- param_dists = {}
39
- with open('quantity_module/data/param_distribution_37.json','r') as fp:
40
- param_dists[37] = json.load(fp)
41
- with open('quantity_module/data/param_distribution_50.json','r') as fp:
42
- param_dists[50] = json.load(fp)
 
 
 
 
43
  # other stuff
44
  df_visc = pd.read_excel('quantity_module/data/solvent-viscosity.xlsx')
45
  df_desc = pd.read_excel(f'quantity_module/data/data-descriptors-mordred-numconfs51.xlsx', usecols=['Solute_InChIKey', 'Vabc','VMcGowan'])
@@ -47,19 +54,29 @@ df_desc = pd.read_excel(f'quantity_module/data/data-descriptors-mordred-numconfs
47
  #df2 = pd.read_excel('quantity_module/data/db-D.xlsx', sheet_name=1) ## XXX?
48
  #df2['CHRIS Class'] = df2['CHRIS Class'].fillna('none')
49
  #df2['New Class'] = df2['New Class'].fillna('none')
50
- ## clean data
51
- df_final_37 = pd.read_excel('quantity_module/data/db-D-interp-37-clean.xlsx')
52
- df_final_50 = pd.read_excel('quantity_module/data/db-D-interp-50-clean.xlsx')
53
- # convert all T to K
54
- df_final_37['T'] = df_final_37['T'] + 273.15
55
- df_final_37['Polymer_Tg'] = df_final_37['Polymer_Tg'] + 273.15
56
- df_final_37['Polymer_Tm'] = df_final_37['Polymer_Tm'] + 273.15
57
- df_final_50['T'] = df_final_50['T'] + 273.15
58
- df_final_50['Polymer_Tg'] = df_final_50['Polymer_Tg'] + 273.15
59
- df_final_50['Polymer_Tm'] = df_final_50['Polymer_Tm'] + 273.15
60
- # add volumes
61
- df_final_37 = pd.merge(df_final_37, df_desc[['Solute_InChIKey', 'Vabc', 'VMcGowan']], how='left', on='Solute_InChIKey', suffixes=('', '_dupe'))
62
- df_final_50 = pd.merge(df_final_50, df_desc[['Solute_InChIKey', 'Vabc', 'VMcGowan']], how='left', on='Solute_InChIKey', suffixes=('', '_dupe'))
 
 
 
 
 
 
 
 
 
 
63
 
64
  #### solvent-specific viscosity
65
  # add MW
@@ -100,30 +117,43 @@ def get_WC(T,solv,V):
100
  return D_WC, eta, params['MW']
101
 
102
  #### add Wilke-Chang
103
- ## 50 C
104
- # estimate Vabc for those with nan values
105
- m = pd.isna(df_final_50['Vabc'])
106
- v = np.polyval(popt_V, df_final_50['VMcGowan'][m])
107
- df_final_50.loc[m, 'Vabc'] = v
108
- T = df_final_50['T']
109
- V = df_final_50['Vabc']
110
- for solv in solvents:
111
- D_WC, eta, MW_solvent = get_WC(T, solv, V)
112
- df_final_50[f'eta_{solv}'] = eta
113
- df_final_50[f'D_WC_{solv}'] = D_WC
114
- df_final_50[f'MW_solvent_{solv}'] = MW_solvent
115
- ## 37 C
116
- # estimate Vabc for those with nan values
117
- m = pd.isna(df_final_37['Vabc'])
118
- v = np.polyval(popt_V, df_final_37['VMcGowan'][m])
119
- df_final_37.loc[m, 'Vabc'] = v
120
- T = df_final_37['T']
121
- V = df_final_37['Vabc']
122
- for solv in solvents:
123
- D_WC, eta, MW_solvent = get_WC(T, solv, V)
124
- df_final_37[f'eta_{solv}'] = eta
125
- df_final_37[f'D_WC_{solv}'] = D_WC
126
- df_final_37[f'MW_solvent_{solv}'] = MW_solvent
 
 
 
 
 
 
 
 
 
 
 
 
 
127
 
128
  def get_V(mol):
129
  calc = mordred.Calculator([mordred.descriptors.VdwVolumeABC, mordred.descriptors.McGowanVolume])
@@ -170,14 +200,17 @@ def get_c_dist(T,Tg,MW):
170
  cs = cs[cs>0]
171
  return cs
172
 
173
- def get_D_dists(w,T,Polymer_Tg,Solvent_Name,Solvent_MW,Solute_MW,CHRIS_category,N=10000,return_DCs=False):
174
  """
175
  D_dist_noswell, D_dist_swell, (c, Ds, DWCs, DCs) = get_D_dists(Swelling_wtfrac,T,Polymer_Tg,Solvent_Name,Solvent_MW,Solute_MW,CHRIS_category,return_DCs=True,N=N)
176
  """
177
- if np.abs(T-310)<2:
178
- df_final_T = df_final_37
179
- if np.abs(T-323)<2:
180
- df_final_T = df_final_50
 
 
 
181
  #Solvent_MW = solvmws[Solvent_Name]
182
  cs = get_c_dist(T,Polymer_Tg,Solvent_MW)
183
  if Solute_MW < 50:
@@ -196,17 +229,34 @@ def get_D_dists(w,T,Polymer_Tg,Solvent_Name,Solvent_MW,Solute_MW,CHRIS_category,
196
  c = rng.choice(cs, N)
197
  lnD_D0 = c*w/(1+(c-1)*w) * np.log(DWCs/Ds)
198
  ## distribution of D_CHRIS
199
- if Solute_MW > 50:
200
- params = param_dists[T-273.15][f'{CHRIS_category}_hi']
201
- else:
202
- params = param_dists[T-273.15][f'{CHRIS_category}_lo']
203
- if params[0] == 'pir':
204
- A_list = params[1:]
205
- D_list = [Piringer(Solute_MW, Ai, T) for Ai in A_list]
 
 
 
 
 
206
  else:
207
- Ball = params[1]
208
- A_list = params[2:]
209
- D_list = np.exp([PowerLaw(Solute_MW, Ai, Ball) for Ai in A_list])
 
 
 
 
 
 
 
 
 
 
 
 
210
  D_dist_noswell = rng.choice(D_list, N)
211
  D_dist_swell = np.exp(np.log(D_dist_noswell)+lnD_D0)
212
  if return_DCs:
 
18
  ### TODO arbitrary T
19
 
20
  ## sampling parameters
21
+ N_sample = int(1e6)
22
+ #rng = np.random.Generator(np.random.PCG64(seed=12345))
23
 
24
  ## list of solvents to include
25
  solvents = sorted(['ethanol', 'isopropanol', 'acetonitrile', 'toluene', 'dichloromethane', 'hexane'])
 
28
  T_cut = 20
29
  MW_cut = 20
30
 
31
+ use_new = True
32
+ T_cut_new = 0.5
33
+
34
  #### read data files
35
  # CHRIS bounds
36
  #with open('quantity_module/data/bounds.pkl', 'rb') as fp:
 
38
  #with open('quantity_module/data/bounds-50.pkl', 'rb') as fp:
39
  # params_dict_ub_50, params_dict_lb_50, params_dict_ub_band_50, params_dict_lb_band_50, params_dict_q95_50, params_dict_q50_50, params_dict_q05_50 = pickle.load(fp)
40
  # CHRIS parameter distributions
41
+ if not use_new:
42
+ param_dists = {}
43
+ with open('quantity_module/data/param_distribution_37.json','r') as fp:
44
+ param_dists[37] = json.load(fp)
45
+ with open('quantity_module/data/param_distribution_50.json','r') as fp:
46
+ param_dists[50] = json.load(fp)
47
+ else:
48
+ with open('quantity_module/data/param_distribution_allT.json','r') as fp:
49
+ param_dists = json.load(fp)
50
  # other stuff
51
  df_visc = pd.read_excel('quantity_module/data/solvent-viscosity.xlsx')
52
  df_desc = pd.read_excel(f'quantity_module/data/data-descriptors-mordred-numconfs51.xlsx', usecols=['Solute_InChIKey', 'Vabc','VMcGowan'])
 
54
  #df2 = pd.read_excel('quantity_module/data/db-D.xlsx', sheet_name=1) ## XXX?
55
  #df2['CHRIS Class'] = df2['CHRIS Class'].fillna('none')
56
  #df2['New Class'] = df2['New Class'].fillna('none')
57
+ if not use_new:
58
+ ## clean data
59
+ df_final_37 = pd.read_excel('quantity_module/data/db-D-interp-37-clean.xlsx')
60
+ df_final_50 = pd.read_excel('quantity_module/data/db-D-interp-50-clean.xlsx')
61
+ # convert all T to K
62
+ df_final_37['T'] = df_final_37['T'] + 273.15
63
+ df_final_37['Polymer_Tg'] = df_final_37['Polymer_Tg'] + 273.15
64
+ df_final_37['Polymer_Tm'] = df_final_37['Polymer_Tm'] + 273.15
65
+ df_final_50['T'] = df_final_50['T'] + 273.15
66
+ df_final_50['Polymer_Tg'] = df_final_50['Polymer_Tg'] + 273.15
67
+ df_final_50['Polymer_Tm'] = df_final_50['Polymer_Tm'] + 273.15
68
+ # add volumes
69
+ df_final_37 = pd.merge(df_final_37, df_desc[['Solute_InChIKey', 'Vabc', 'VMcGowan']], how='left', on='Solute_InChIKey', suffixes=('', '_dupe'))
70
+ df_final_50 = pd.merge(df_final_50, df_desc[['Solute_InChIKey', 'Vabc', 'VMcGowan']], how='left', on='Solute_InChIKey', suffixes=('', '_dupe'))
71
+ else:
72
+ ## clean data
73
+ df_final = pd.read_excel('quantity_module/data/db-D-interp-allT-clean.xlsx')
74
+ # convert all T to K
75
+ df_final['T'] = df_final['T'] + 273.15
76
+ df_final['Polymer_Tg'] = df_final['Polymer_Tg'] + 273.15
77
+ df_final['Polymer_Tm'] = df_final['Polymer_Tm'] + 273.15
78
+ # add volumes
79
+ df_final = pd.merge(df_final, df_desc[['Solute_InChIKey', 'Vabc', 'VMcGowan']], how='left', on='Solute_InChIKey', suffixes=('', '_dupe'))
80
 
81
  #### solvent-specific viscosity
82
  # add MW
 
117
  return D_WC, eta, params['MW']
118
 
119
  #### add Wilke-Chang
120
+ if not use_new:
121
+ ## 50 C
122
+ # estimate Vabc for those with nan values
123
+ m = pd.isna(df_final_50['Vabc'])
124
+ v = np.polyval(popt_V, df_final_50['VMcGowan'][m])
125
+ df_final_50.loc[m, 'Vabc'] = v
126
+ T = df_final_50['T']
127
+ V = df_final_50['Vabc']
128
+ for solv in solvents:
129
+ D_WC, eta, MW_solvent = get_WC(T, solv, V)
130
+ df_final_50[f'eta_{solv}'] = eta
131
+ df_final_50[f'D_WC_{solv}'] = D_WC
132
+ df_final_50[f'MW_solvent_{solv}'] = MW_solvent
133
+ ## 37 C
134
+ # estimate Vabc for those with nan values
135
+ m = pd.isna(df_final_37['Vabc'])
136
+ v = np.polyval(popt_V, df_final_37['VMcGowan'][m])
137
+ df_final_37.loc[m, 'Vabc'] = v
138
+ T = df_final_37['T']
139
+ V = df_final_37['Vabc']
140
+ for solv in solvents:
141
+ D_WC, eta, MW_solvent = get_WC(T, solv, V)
142
+ df_final_37[f'eta_{solv}'] = eta
143
+ df_final_37[f'D_WC_{solv}'] = D_WC
144
+ df_final_37[f'MW_solvent_{solv}'] = MW_solvent
145
+ else:
146
+ # estimate Vabc for those with nan values
147
+ m = pd.isna(df_final['Vabc'])
148
+ v = np.polyval(popt_V, df_final['VMcGowan'][m])
149
+ df_final.loc[m, 'Vabc'] = v
150
+ T = df_final['T']
151
+ V = df_final['Vabc']
152
+ for solv in solvents:
153
+ D_WC, eta, MW_solvent = get_WC(T, solv, V)
154
+ df_final[f'eta_{solv}'] = eta
155
+ df_final[f'D_WC_{solv}'] = D_WC
156
+ df_final[f'MW_solvent_{solv}'] = MW_solvent
157
 
158
  def get_V(mol):
159
  calc = mordred.Calculator([mordred.descriptors.VdwVolumeABC, mordred.descriptors.McGowanVolume])
 
200
  cs = cs[cs>0]
201
  return cs
202
 
203
+ def get_D_dists(w,T,Polymer_Tg,Solvent_Name,Solvent_MW,Solute_MW,CHRIS_category,rng,N=100000,return_DCs=False):
204
  """
205
  D_dist_noswell, D_dist_swell, (c, Ds, DWCs, DCs) = get_D_dists(Swelling_wtfrac,T,Polymer_Tg,Solvent_Name,Solvent_MW,Solute_MW,CHRIS_category,return_DCs=True,N=N)
206
  """
207
+ if not use_new:
208
+ if np.abs(T-310)<2:
209
+ df_final_T = df_final_37
210
+ if np.abs(T-323)<2:
211
+ df_final_T = df_final_50
212
+ else:
213
+ df_final_T = df_final.loc[np.abs(df_final['T']-T)<T_cut_new]
214
  #Solvent_MW = solvmws[Solvent_Name]
215
  cs = get_c_dist(T,Polymer_Tg,Solvent_MW)
216
  if Solute_MW < 50:
 
229
  c = rng.choice(cs, N)
230
  lnD_D0 = c*w/(1+(c-1)*w) * np.log(DWCs/Ds)
231
  ## distribution of D_CHRIS
232
+ if not use_new:
233
+ if Solute_MW > 50:
234
+ params = param_dists[T-273.15][f'{CHRIS_category}_hi']
235
+ else:
236
+ params = param_dists[T-273.15][f'{CHRIS_category}_lo']
237
+ if params[0] == 'pir':
238
+ A_list = params[1:]
239
+ D_list = [Piringer(Solute_MW, Ai, T) for Ai in A_list]
240
+ else:
241
+ Ball = params[1]
242
+ A_list = params[2:]
243
+ D_list = np.exp([PowerLaw(Solute_MW, Ai, Ball) for Ai in A_list])
244
  else:
245
+ if Solute_MW > 50:
246
+ subkey = f'{CHRIS_category}_hi'
247
+ else:
248
+ subkey = f'{CHRIS_category}_lo'
249
+ allparams = [param_dists[Ti][subkey] for Ti in param_dists if T+T_cut_new >= int(Ti)+273.15 >= T-T_cut_new]
250
+ #allparams = [param_dists[Ti][subkey] for Ti in param_dists if T+T_cut_new > int(Ti)+273.15 > T-T_cut_new]
251
+ D_list = []
252
+ for params in allparams:
253
+ if params[0] == 'pir':
254
+ A_list = params[1:]
255
+ D_list += [Piringer(Solute_MW, Ai, T) for Ai in A_list]
256
+ else:
257
+ Ball = params[1]
258
+ A_list = params[2:]
259
+ D_list += list(np.exp([PowerLaw(Solute_MW, Ai, Ball) for Ai in A_list]))
260
  D_dist_noswell = rng.choice(D_list, N)
261
  D_dist_swell = np.exp(np.log(D_dist_noswell)+lnD_D0)
262
  if return_DCs:
quantity_module/data/bounds.pkl DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:832efc7ec94168572bd7e6c33fddfeb61974ecbb0b9b5cc88165042943fe11c1
3
- size 4415
 
 
 
 
quantity_module/data/copy-data.sh CHANGED
@@ -1,11 +1,18 @@
 
 
1
  cp /Users/robert.elder/doc/experiments/01-diffusion/data/solvent-viscosity.xlsx .
2
  cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/descs/data-descriptors-mordred-numconfs51.xlsx .
3
- cp /Users/robert.elder/doc/experiments/08-solubility/data/db-properties-polymer.xlsx .
4
- cp /Users/robert.elder/doc/experiments/01-diffusion/data/db-D.xlsx .
5
- cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/bounds.pkl .
6
- cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/bounds-50.pkl .
7
- cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/param_distribution_37.json .
8
- cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/param_distribution_50.json .
9
- cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/db-D-interp-37-clean.xlsx .
10
- cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/db-D-interp-50-clean.xlsx .
11
  cp /Users/robert.elder/doc/experiments/01-diffusion/data/vrentas-duda-params.xlsx .
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
  cp /Users/robert.elder/doc/experiments/01-diffusion/data/solvent-viscosity.xlsx .
4
  cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/descs/data-descriptors-mordred-numconfs51.xlsx .
5
+ cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/param_distribution_allT.json .
6
+ cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/db-D-interp-allT-clean.xlsx .
 
 
 
 
 
 
7
  cp /Users/robert.elder/doc/experiments/01-diffusion/data/vrentas-duda-params.xlsx .
8
+
9
+ if 0;
10
+ cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/param_distribution_37.json .
11
+ cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/param_distribution_50.json .
12
+ cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/db-D-interp-37-clean.xlsx .
13
+ cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/db-D-interp-50-clean.xlsx .
14
+ #cp /Users/robert.elder/doc/experiments/08-solubility/data/db-properties-polymer.xlsx .
15
+ #cp /Users/robert.elder/doc/experiments/01-diffusion/data/db-D.xlsx .
16
+ #cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/bounds.pkl .
17
+ #cp /Users/robert.elder/doc/experiments/01-diffusion/data/tmp-plots/bounds-99/bounds-50.pkl .
18
+ fi
quantity_module/data/db-D-interp-37-clean.xlsx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:732cb2fcdb899e19db62c78867d35bce4121161590f3605a7ca1f9d1ad5fed95
3
- size 545996
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:5c046f45ede6419629615b2e7397d74dc22c19db8556853638dd0c54ef9fad3f
3
+ size 546002
quantity_module/data/db-D-interp-50-clean.xlsx CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:7bd693f5a6ed0d1ff2328f51e7ecedfbe02a8d0e406b24b94463450745418848
3
- size 314788
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:2086bf5769a47fb6ff95f4f1dc4368788f03d6365300e5c40f6631798929116d
3
+ size 314780
quantity_module/data/{bounds-50.pkl → db-D-interp-allT-clean.xlsx} RENAMED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:9afeec8bdcf1cd43f7a1842b81e342964dab815d826200708d99400a9ce057c1
3
- size 4415
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b34934f1be324e95766d57b156ee29822b1ed282b3f0b4402e5405a8752c8acd
3
+ size 10436853
quantity_module/data/db-D.xlsx DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:713467099d12a6482f175c3c0d402bf84829bec0319c33708fcef257a8ce0aa3
3
- size 884005
 
 
 
 
quantity_module/data/db-properties-polymer.xlsx DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:c7a9829f683c9e0de82cc5ec8501afc71e11e0614e42dda7795d03cc70d82b32
3
- size 37454
 
 
 
 
quantity_module/data/param_distribution_allT.json ADDED
The diff for this file is too large to render. See raw diff
 
quantity_module/quantity.py CHANGED
@@ -2,7 +2,7 @@ import sys,numbers
2
  import numpy as np
3
  import pandas as pd
4
  from flask import render_template, request
5
- from functions import SigFigs, Piringer, WilkeChang, CdfPlot
6
  from functions import Piecewise, PowerLaw
7
  from qrf_functions import QRF_Apply, QRF_Ceramic
8
  from . import blueprint
@@ -123,9 +123,10 @@ def exp_post():
123
  ## use categories
124
  ## TODO what is the correct worst-case for a generic polymer? G2?
125
  CHRIS_category = categories[pIndex]
126
- D_dist_noswell, D_dist_swell = get_D_dists(Swelling_wtfrac, T+273.15, Polymer_Tg+273.15, SolventName, SolventMW, SoluteMW, CHRIS_category, return_DCs=False, N=N_sample)
 
127
  M0_pred = get_M_dist(D_dist_swell, M_expt, PolymerVolume, SurfaceArea, SolventVolume, ExtractionTime*3600, K_expt=K_expt)
128
- if 1:
129
  print('Swelling_wtfrac, T+273.15, Polymer_Tg+273.15, SolventName, SolventMW, SoluteMW, CHRIS_category')
130
  print(Swelling_wtfrac, T+273.15, Polymer_Tg+273.15, SolventName, SolventMW, SoluteMW, CHRIS_category)
131
  print(np.nanquantile(D_dist_swell, [0.05,0.5,0.95]))
@@ -138,33 +139,21 @@ def exp_post():
138
  method = 'wc'
139
 
140
  # Generate the rate plot using matplotlib
141
- pngImageB64String = CdfPlot(M0_pred[~np.isnan(M0_pred)], units=units)
142
- # release = SheetRelease(amount, vol, area, time, diff)
143
- #
144
- # MOS = TTC / release
145
- #
146
- # # Generate the rate plot using matplotlib
147
- # #tarray = np.arange(1., 31., 1.)
148
- # #rates = SheetRates(amount, vol, area, tarray, diff)
149
- # #pngImageB64String = RatePlot(tarray, rates)
150
- #
151
- # # Generate cumulative release at given times
152
- # tlist = [1.]
153
- # if len(request.form.getlist('exposure30d')):
154
- # tlist.append(30.)
155
- # if len(request.form.getlist('exposureuser')):
156
- # tlist.append(float(request.form['exposureuserbox']))
157
- # release_list = [SheetRelease(amount, vol, area, t*24, diff) for t in tlist]
158
- # #print(release_list, file=sys.stderr)
159
- # frac_list = [str(SigFigs(r/amount*100,2))+'%' for r in release_list]
160
- # release_list = [SigFigs(r,2) for r in release_list]
161
- # df_rel = pd.DataFrame(np.transpose([tlist,release_list,frac_list]),columns=['Exposure duration (days)',f'Cumulative exposure ({units})','Cumulative exposure (%)'])
162
- # df_rel['Exposure duration (days)'] = df_rel['Exposure duration (days)'].astype(float)
163
- # release_table = df_rel.sort_values(by='Exposure duration (days)').to_html(index=False)
164
- #
165
- # release = SigFigs(release, 2)
166
- # MOS = SigFigs(MOS, 2)
167
- # diff = SigFigs(diff, 2)
168
 
169
  tau = np.nanquantile(D_dist_swell,0.5) * (ExtractionTime*3600) / (PolymerVolume/SurfaceArea)**2
170
 
@@ -172,9 +161,9 @@ def exp_post():
172
  tau_out = SigFigs(tau,6)
173
 
174
  return render_template('quantity_report.html', show_properties=show_properties, polymers=polymers, pIndex=pIndex,
175
- area=SurfaceArea, vol=PolymerVolume, units=units, M=M_expt, M0=M0_out, time=ExtractionTime, image=pngImageB64String,
176
  solventvol=SolventVolume, solventname=SolventName, swelling=Swelling_percent, K=K_expt, T=T, tau=tau_out,
177
- chemName=chemName, MW=MW, LogP=LogP, rho=rho, mp=mp, iupac=iupac, cas=cas, smiles=smiles, molImage=molImage,
178
  LogP_origin=LogP_origin, rho_origin=rho_origin, mp_origin=mp_origin, ceramic=is_ceramic, methods=[method,Polymer_Tg,PolymerDensity],
179
  mass=mass, density=PolymerDensity)
180
 
 
2
  import numpy as np
3
  import pandas as pd
4
  from flask import render_template, request
5
+ from functions import SigFigs, HtmlNumber, Piringer, WilkeChang, CdfPlot
6
  from functions import Piecewise, PowerLaw
7
  from qrf_functions import QRF_Apply, QRF_Ceramic
8
  from . import blueprint
 
123
  ## use categories
124
  ## TODO what is the correct worst-case for a generic polymer? G2?
125
  CHRIS_category = categories[pIndex]
126
+ rng = np.random.Generator(np.random.PCG64(seed=12345))
127
+ D_dist_noswell, D_dist_swell = get_D_dists(Swelling_wtfrac, T+273.15, Polymer_Tg+273.15, SolventName, SolventMW, SoluteMW, CHRIS_category, rng, return_DCs=False, N=N_sample)
128
  M0_pred = get_M_dist(D_dist_swell, M_expt, PolymerVolume, SurfaceArea, SolventVolume, ExtractionTime*3600, K_expt=K_expt)
129
+ if 0:
130
  print('Swelling_wtfrac, T+273.15, Polymer_Tg+273.15, SolventName, SolventMW, SoluteMW, CHRIS_category')
131
  print(Swelling_wtfrac, T+273.15, Polymer_Tg+273.15, SolventName, SolventMW, SoluteMW, CHRIS_category)
132
  print(np.nanquantile(D_dist_swell, [0.05,0.5,0.95]))
 
139
  method = 'wc'
140
 
141
  # Generate the rate plot using matplotlib
142
+ #pngImageB64String = CdfPlot(M0_pred[~np.isnan(M0_pred)], units=units)
143
+
144
+ # table of percentiles
145
+ #vals = np.quantile(M0_pred, [0.05,0.25,0.5,0.75,0.95])
146
+ #vals = [SigFigs(v,6) for v in np.quantile(M0_pred, [0.05,0.25,0.5,0.75,0.95])]
147
+ vals = np.quantile(M0_pred, [0.05,0.25,0.5,0.75,0.95])
148
+ pcts = np.round(vals/vals[2]*100,1)
149
+ vals = [HtmlNumber(v,3) for v in vals]
150
+ #df_table = pd.DataFrame(data=np.array([[r'5<sup>th</sup>', r'25<sup>th</sup>', r'50<sup>th</sup> (median)', r'75<sup>th</sup>', r'95<sup>th</sup>'], vals]).T, columns=[r'Percentile', r'\( M_0 \)'])
151
+ df_table = pd.DataFrame(data=np.array([[r'5<sup>th</sup>', r'25<sup>th</sup>', r'50<sup>th</sup> (median)', r'75<sup>th</sup>', r'95<sup>th</sup>'], vals, pcts]).T, columns=[r'Percentile', r'\( M_0 \)'+f' ({units})', r'\( M_0 \) (% median)'])
152
+ #df_table[r'\( M_0 \)'] = df_table[r'\( M_0 \)'].astype(float)
153
+ #table = df_table.to_html(index=False, escape=False, justify='center')
154
+ #table = df_table.style.set_properties(subset=['\( M_0 \)'], **{'text-align': 'right'}).format(lambda x: f"{float(x):.3f}", subset='\( M_0 \)').set_table_attributes('border="1"').hide(axis='index').to_html(index=False, escape=False, justify='center')
155
+ table = df_table.style.set_properties(subset=[f'\( M_0 \) ({units})', r'\( M_0 \) (% median)'], **{'text-align': 'right'}).set_table_attributes('border="1"').hide(axis='index').to_html(index=False, escape=False, justify='center')
156
+ #print(table)
 
 
 
 
 
 
 
 
 
 
 
 
157
 
158
  tau = np.nanquantile(D_dist_swell,0.5) * (ExtractionTime*3600) / (PolymerVolume/SurfaceArea)**2
159
 
 
161
  tau_out = SigFigs(tau,6)
162
 
163
  return render_template('quantity_report.html', show_properties=show_properties, polymers=polymers, pIndex=pIndex,
164
+ area=SurfaceArea, vol=PolymerVolume, units=units, M=M_expt, M0=M0_out, time=ExtractionTime,
165
  solventvol=SolventVolume, solventname=SolventName, swelling=Swelling_percent, K=K_expt, T=T, tau=tau_out,
166
+ chemName=chemName, MW=MW, LogP=LogP, rho=rho, mp=mp, iupac=iupac, cas=cas, smiles=smiles, molImage=molImage, table=table,
167
  LogP_origin=LogP_origin, rho_origin=rho_origin, mp_origin=mp_origin, ceramic=is_ceramic, methods=[method,Polymer_Tg,PolymerDensity],
168
  mass=mass, density=PolymerDensity)
169
 
quantity_module/templates/quantity_index.html CHANGED
@@ -11,6 +11,12 @@
11
  <script src="{{ url_for('static',filename='bootstrap.min.js') }}" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script>
12
  <link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='styles.css') }}">
13
 
 
 
 
 
 
 
14
  </head>
15
 
16
  <img src="{{ url_for('static',filename='images/FDAlogo.png') }}" style="float: left;" height="100"/>
@@ -31,22 +37,33 @@ For a history of updates, please see the <a href="{{url_for('.static', filename=
31
 
32
  <!-- Chemical input section -->
33
 
34
- <h3> Chemical identity <button type="button" class="Info_btn" data-toggle="modal" data-target="#LeachModal" >&#9432;</button> </h3>
35
-
36
- Identifier type: <select name="IDtype">
37
- <option value="CAS" selected>CAS</option>
38
- <option value="SMILES" >SMILES</option>
39
- <option value="common" >Common name</option>
40
- </select> <br>
41
-
42
- Identifier: <input name="chemName" id="chemName" type="text" value="" required><br>
43
-
44
- Extracted amount: <input name="amount" id="amount" value="1.0" step="any" min="0.000001" type="number" required>
45
- Amount units: <select name="units">
46
- <option value="mg" selected>mg</option>
47
- <option value="µg">µg</option>
48
- </select>
49
- <br>
 
 
 
 
 
 
 
 
 
 
 
50
 
51
  <!-- Modal -->
52
  <div id="LeachModal" class="modal fade" role="dialog">
@@ -77,17 +94,29 @@ For a history of updates, please see the <a href="{{url_for('.static', filename=
77
 
78
  <!-- Polymer matrix input section -->
79
 
80
- <h3> Polymer matrix <button type="button" class="Info_btn" data-toggle="modal" data-target="#PolymerModal">&#9432;</button> </h3>
81
- Matrix: <select name="polymer" id="polymer">
82
- <option value="{{polymers[0]}}" selected>{{polymers[0]}}</option>
83
- {% for polymer in polymers[1:] %}
84
- <option value="{{polymer}}">{{polymer}}</option>
85
- {% endfor %}
86
- </select> <br>
87
- Mass (g): <input name="mass" id="mass" step="any" value="1.0" min="0.000001" type="number" required> <br>
88
- Density (g/cm<sup>3</sup>): <input name="density" id="density" step="any" value="1.0" min="0.01" type="number" required><br>
89
- Glass transition temperature (&deg;C): <input name="Polymer_Tg" id="Polymer_Tg" step="any" min="-273.15" max="500" value="0" type="number" required><br>
90
- </span>
 
 
 
 
 
 
 
 
 
 
 
 
91
 
92
  <!-- Modal -->
93
  <div id="PolymerModal" class="modal fade" role="dialog">
@@ -115,19 +144,21 @@ Glass transition temperature (&deg;C): <input name="Polymer_Tg" id="Polymer_Tg"
115
 
116
  <!-- Extraction parameters input section -->
117
 
118
- <h3> Extraction parameters <button type=button class="Info_btn" data-toggle="modal" data-target="#ExtractionModal">&#9432;</button> </h3>
119
- Device surface area (cm<sup>2</sup>): <input name="area" id="area" step="any" value="5.0" min="0.001" type="number" required><br>
120
- Duration (hours): <input name="time" id="time" step="any" value="24.0" min="0.001" type="number" required><br>
121
- Temperature (&deg;C): <input name="T" id="T" step="any" value="50.0" min="-273.15" type="number" required><br>
122
- Solvent: <select name="solventname" id="solventname">
123
- <option value="{{solvents[0]}}" selected>{{solvents[0]}}</option>
124
- {% for solvent in solvents[1:] %}
125
- <option value="{{solvent}}">{{solvent}}</option>
126
- {% endfor %}
127
- </select> <br>
128
- Solvent volume (mL): <input name="solventvol" id="solventvol" step="any" value="10.0" min="0.001" type="number" required><br>
129
- Swelling amount (wt%): <input name="swelling" id="swelling" step="any" value="10.0" max="100.0" min="0.0" type="number" required><br>
130
- Partition coefficient: <input name="K" id="K" step="any" value="10.0" min="0.00001" type="number" required><br>
 
 
131
 
132
  <!-- Modal -->
133
  <div id="ExtractionModal" class="modal fade" role="dialog">
@@ -160,7 +191,7 @@ Glass transition temperature (&deg;C): <input name="Polymer_Tg" id="Polymer_Tg"
160
  <p><button type="submit">Estimate total quantity</button></p>
161
 
162
  <!--
163
- <h3> Exposure assessment <button type=button class="Info_btn" data-toggle="modal" data-target="#ExposureModal">&#9432;</button> </h3>
164
  Cumulative exposure duration(s):
165
  <input type="checkbox" id="exposure1d" name="exposure1d" value="exposure1d" checked=True, disabled=True>
166
  <label for="exposure1d">1 day</label>
 
11
  <script src="{{ url_for('static',filename='bootstrap.min.js') }}" integrity="sha384-VHvPCCyXqtD5DqJeNxl2dtTyhF78xXNXdkwX1CZeRusQfRKp+tA7hAShOK/B/fQ2" crossorigin="anonymous"></script>
12
  <link rel= "stylesheet" type= "text/css" href= "{{ url_for('static',filename='styles.css') }}">
13
 
14
+ <style>
15
+ table, th, td {
16
+ border: 1px solid black;
17
+ text-align: left;
18
+ }
19
+ </style>
20
  </head>
21
 
22
  <img src="{{ url_for('static',filename='images/FDAlogo.png') }}" style="float: left;" height="100"/>
 
37
 
38
  <!-- Chemical input section -->
39
 
40
+ <table id="table1" align="left" style="float: none;">
41
+ <tr><td colspan="2"><h4> Chemical identity <button type="button" class="Info_btn" data-toggle="modal" data-target="#LeachModal" >&#9432;</button> </h4></td></tr>
42
+
43
+ <tr>
44
+ <th> Identifier type </th>
45
+ <td> <select name="IDtype">
46
+ <option value="CAS" selected>CAS</option>
47
+ <option value="SMILES" >SMILES</option>
48
+ <option value="common" >Common name</option>
49
+ </select>
50
+ </td>
51
+ </tr>
52
+ <tr>
53
+ <th>Identifier</th>
54
+ <td><input name="chemName" id="chemName" type="text" value="" required></td>
55
+ </tr>
56
+ </tr>
57
+ <th>Extracted amount</th>
58
+ <td><input name="amount" id="amount" value="1.0" step="any" min="0.000001" type="number" required></td>
59
+ </tr>
60
+ </tr>
61
+ <th> Amount units</th>
62
+ <td><select name="units">
63
+ <option value="mg" selected>mg</option>
64
+ <option value="µg">µg</option>
65
+ </select>
66
+ </td>
67
 
68
  <!-- Modal -->
69
  <div id="LeachModal" class="modal fade" role="dialog">
 
94
 
95
  <!-- Polymer matrix input section -->
96
 
97
+ <tr><td colspan="2"><h4> Polymer matrix <button type="button" class="Info_btn" data-toggle="modal" data-target="#PolymerModal">&#9432;</button> </td></tr></h4>
98
+ <tr>
99
+ <th>Matrix</th>
100
+ <td><select name="polymer" id="polymer" style="max-width:20em;">
101
+ <option value="{{polymers[0]}}" selected>{{polymers[0]}}</option>
102
+ {% for polymer in polymers[1:] %}
103
+ <option value="{{polymer}}">{{polymer}}</option>
104
+ {% endfor %}
105
+ </select>
106
+ </td>
107
+ </tr>
108
+ <tr>
109
+ <th>Mass (g)</th>
110
+ <td><input name="mass" id="mass" step="any" value="1.0" min="0.000001" type="number" required> </td>
111
+ </tr>
112
+ <tr>
113
+ <th>Density (g/cm<sup>3</sup>)</th>
114
+ <td><input name="density" id="density" step="any" value="1.0" min="0.01" type="number" required></td>
115
+ </tr>
116
+ <tr>
117
+ <th>Glass transition temperature (&deg;C)</th>
118
+ <td><input name="Polymer_Tg" id="Polymer_Tg" step="any" min="-273.15" max="500" value="0" type="number" required></td>
119
+ </tr>
120
 
121
  <!-- Modal -->
122
  <div id="PolymerModal" class="modal fade" role="dialog">
 
144
 
145
  <!-- Extraction parameters input section -->
146
 
147
+ <tr><td colspan="2"><h4> Extraction parameters <button type=button class="Info_btn" data-toggle="modal" data-target="#ExtractionModal">&#9432;</button></td></tr> </h4>
148
+ <tr><th>Device surface area (cm<sup>2</sup>)</th><td> <input name="area" id="area" step="any" value="5.0" min="0.001" type="number" required></td></tr>
149
+ <tr><th>Duration (hours)</th><td> <input name="time" id="time" step="any" value="24.0" min="0.001" type="number" required></td></tr>
150
+ <tr><th>Temperature (&deg;C)</th><td> <input name="T" id="T" step="any" value="50.0" min="20" max="75" type="number" required></td></tr>
151
+ <tr><th>Solvent</th>
152
+ <td> <select name="solventname" id="solventname">
153
+ <option value="{{solvents[0]}}" selected>{{solvents[0]}}</option>
154
+ {% for solvent in solvents[1:] %}
155
+ <option value="{{solvent}}">{{solvent}}</option>
156
+ {% endfor %}
157
+ </select> </td></tr>
158
+ <tr><th>Solvent volume (mL)</th><td> <input name="solventvol" id="solventvol" step="any" value="10.0" min="0.001" type="number" required></td></tr>
159
+ <tr><th>Swelling amount (wt%)</th><td> <input name="swelling" id="swelling" step="any" value="10.0" max="100.0" min="0.0" type="number" required></td></tr>
160
+ <tr><th>Partition coefficient</th><td> <input name="K" id="K" step="any" value="10.0" min="0.00001" type="number" required></td></tr>
161
+ </table>
162
 
163
  <!-- Modal -->
164
  <div id="ExtractionModal" class="modal fade" role="dialog">
 
191
  <p><button type="submit">Estimate total quantity</button></p>
192
 
193
  <!--
194
+ <h4> Exposure assessment <button type=button class="Info_btn" data-toggle="modal" data-target="#ExposureModal">&#9432;</button> </h4>
195
  Cumulative exposure duration(s):
196
  <input type="checkbox" id="exposure1d" name="exposure1d" value="exposure1d" checked=True, disabled=True>
197
  <label for="exposure1d">1 day</label>
quantity_module/templates/quantity_report.html CHANGED
@@ -75,13 +75,13 @@
75
 
76
  <p>
77
  {% if methods[0]=="category" %}
78
- <u> Modeling extraction from {{polymers[pIndex]}} estimates the total quantity \( M_0 \) = {{M0}} {{units}}. </u>
79
  {% elif methods[0]=="wc" %}
80
- <u> Modeling extraction from the polymer (with worst-case diffusion in water assumed because no T<sub>g</sub> was specified) estimates the total quantity \( M_0 \) = {{M0}} {{units}}. </u>
81
  {% elif methods[0]=="qrf" %}
82
- <u> Modeling extraction from the polymer (with density = {{methods[2]}} g/cm<sup>3</sup> and T<sub>g</sub> = {{methods[1]}} &deg;C) estimates the total quantity \( M_0 \) = {{M0}} {{units}}. </u>
83
  {% elif methods[0]=="qrf/wc" %}
84
- <u> Modeling extraction from the polymer (with worst-case diffusion in water assumed because this system is outside the model training domain) estimates the total quantity \( M_0 \) = {{M0}} {{units}}. </u>
85
  {% endif %}
86
  <p>
87
 
@@ -111,18 +111,23 @@ where \( \tau= D t A^2 / V^2 \) and \( A \) and \( V \) are the surface area and
111
  It can be helpful to examine the cumulative amount released over various exposure durations. For example, the 30-day cumulative exposure may be compared to the TSL<sub>&le;30 d</sub> as recommended in ISO 10993-17:2023. Note that this calculation assumes a single device is used for the entire duration. If multiple devices are used concurrently or sequentially (e.g., daily replacement), this should be accounted for during toxicological risk assessment.
112
  </p>
113
 
 
114
  <div>
115
  {{ release_table | safe }}
116
  </div> -->
117
 
118
  <p>
119
- This estimate was derived using the solution to the conservative plane sheet model for mass release, \( M \):
120
 
121
  \[
122
- \frac{M}{M_0} = \frac{1}{1+1/\Psi}\left[1-\sum^\infty_{n=1} \frac{2\Psi(1+\Psi)}{1+\Psi+\Psi^2q_n^2}\exp\left(-\tau q_n^2\right)\right]
 
 
 
 
123
  \]
124
 
125
- where \( \tau= D t A^2 / V_p^2 \), \( A \) and \( V_p \) are the surface area and volume of the polymer matrix, respectively, \( D \) is a conservative estimate of the diffusion coefficient of the extractable within the polymer matrix, and \( t \) is time. The quantity \( \Psi = V_s/V_p K \), where \( V_s \) is the solvent volume and \( K \) is the polymer-solvent partition coefficient for the extractable. \( M_0 \) is total quantity initially contained in the polymer. Based on the input provided, the calculation used the following values:
126
  </p>
127
 
128
  <p>
@@ -132,14 +137,16 @@ Duration \( t \) = {{time}} h <br>
132
  Temperature = {{T}} &deg;C <br>
133
  Solvent = {{solventname}} <br>
134
  Solvent volume \( V_s \) = {{solventvol}} cm<sup>3</sup> <br>
135
- Polymer volume \( V_p \) = {{vol}} cm<sup>3</sup> (based on polymer mass = {{mass}} g and density = {{density}} g/cm<sup>3</sup> <br>
136
- Partition coefficient = {{K}} <br>
137
  Swelling = {{swelling}} wt% (used to estimate \( D \))<br>
138
  </p>
139
 
140
- <p>The total quantity reported above is the median of this predicted distribution:</p>
141
 
142
- <p><img src="{{ image }}"/></p>
 
 
143
 
144
  <p>The progress of the extraction can be expressed through the dimensionless time \( \tau = Dt/L^2 \), where \( D \) is the solute diffusivity in the polymer, \( t \) is the extraction duration, and \( L = V_p/A \) is the characteristic length scale. For your extraction, \( \tau \) = {{tau}}. Extractions with \( \tau \) &geq; 0.1 result in more accurate estimates of the total quantity, and when \( \tau \) &geq; 1.0 the extracted amount may be used directly as the total quantity.</p>
145
 
 
75
 
76
  <p>
77
  {% if methods[0]=="category" %}
78
+ <u> Modeling extraction from {{polymers[pIndex]}} estimates the total quantity = {{M0}} {{units}}. </u>
79
  {% elif methods[0]=="wc" %}
80
+ <u> Modeling extraction from the polymer (with worst-case diffusion in water assumed because no T<sub>g</sub> was specified) estimates the total quantity = {{M0}} {{units}}. </u>
81
  {% elif methods[0]=="qrf" %}
82
+ <u> Modeling extraction from the polymer (with density = {{methods[2]}} g/cm<sup>3</sup> and T<sub>g</sub> = {{methods[1]}} &deg;C) estimates the total quantity = {{M0}} {{units}}. </u>
83
  {% elif methods[0]=="qrf/wc" %}
84
+ <u> Modeling extraction from the polymer (with worst-case diffusion in water assumed because this system is outside the model training domain) estimates the total quantity = {{M0}} {{units}}. </u>
85
  {% endif %}
86
  <p>
87
 
 
111
  It can be helpful to examine the cumulative amount released over various exposure durations. For example, the 30-day cumulative exposure may be compared to the TSL<sub>&le;30 d</sub> as recommended in ISO 10993-17:2023. Note that this calculation assumes a single device is used for the entire duration. If multiple devices are used concurrently or sequentially (e.g., daily replacement), this should be accounted for during toxicological risk assessment.
112
  </p>
113
 
114
+ (1.+alpha)*(1.-np.exp(T/alpha**2.)*sp.special.erfc(np.sqrt(T)/alpha))
115
  <div>
116
  {{ release_table | safe }}
117
  </div> -->
118
 
119
  <p>
120
+ This estimate was derived using the solutions to the conservative plane sheet model for mass release, \( M \):
121
 
122
  \[
123
+ \frac{M(\tau)}{M_0} = \left\{
124
+ \begin{array}{cr}
125
+ (1+\Psi) \left[1-\exp\left( \frac{\tau}{\alpha^2} \right) \mathop{\rm erfc} \left( \frac{\tau^{0.5}}{\Psi} \right) \right] & \tau \leq 0.05 \\
126
+ \frac{1}{1+1/\Psi}\left[1-\sum^\infty_{n=1} \frac{2\Psi(1+\Psi)}{1+\Psi+\Psi^2q_n^2}\exp\left(-\tau q_n^2\right)\right] & \tau > 0.05
127
+ \end{array} \right.
128
  \]
129
 
130
+ where \( \tau= D t A^2 / V_p^2 \), \( A \) and \( V_p \) are the surface area and volume of the polymer matrix, respectively, \( D \) is a conservative estimate of the diffusion coefficient of the extractable within the polymer matrix, and \( t \) is time. The quantity \( \Psi = V_s/V_p K \), where \( V_s \) is the solvent volume and \( K \) is the polymer-solvent partition coefficient for the extractable. \( q_n \) are the roots of \( \tan x + \Psi x \). \( M_0 \) is total quantity initially contained in the polymer. Based on the input provided, the calculation used the following values:
131
  </p>
132
 
133
  <p>
 
137
  Temperature = {{T}} &deg;C <br>
138
  Solvent = {{solventname}} <br>
139
  Solvent volume \( V_s \) = {{solventvol}} cm<sup>3</sup> <br>
140
+ Polymer volume \( V_p \) = {{vol}} cm<sup>3</sup> (based on polymer mass = {{mass}} g and density = {{density}} g/cm<sup>3</sup>) <br>
141
+ Partition coefficient \( K \)= {{K}} <br>
142
  Swelling = {{swelling}} wt% (used to estimate \( D \))<br>
143
  </p>
144
 
145
+ <p>The total quantity reported above is the median of the distribution of predicted amounts. Additional percentiles are provided here for informational purposes:</p>
146
 
147
+ <div>
148
+ {{ table | safe }}
149
+ </div>
150
 
151
  <p>The progress of the extraction can be expressed through the dimensionless time \( \tau = Dt/L^2 \), where \( D \) is the solute diffusivity in the polymer, \( t \) is the extraction duration, and \( L = V_p/A \) is the characteristic length scale. For your extraction, \( \tau \) = {{tau}}. Extractions with \( \tau \) &geq; 0.1 result in more accurate estimates of the total quantity, and when \( \tau \) &geq; 1.0 the extracted amount may be used directly as the total quantity.</p>
152