htpolynet 2.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (110) hide show
  1. htpolynet/__init__.py +2 -0
  2. htpolynet/analysis/__init__.py +0 -0
  3. htpolynet/analysis/analyze.py +256 -0
  4. htpolynet/analysis/plot.py +588 -0
  5. htpolynet/analysis/postsim.py +444 -0
  6. htpolynet/analysis/utils.py +399 -0
  7. htpolynet/cli.py +488 -0
  8. htpolynet/core/__init__.py +0 -0
  9. htpolynet/core/bondtemplate.py +112 -0
  10. htpolynet/core/configuration.py +83 -0
  11. htpolynet/core/coordinates.py +644 -0
  12. htpolynet/core/molecule.py +998 -0
  13. htpolynet/core/projectfilesystem.py +616 -0
  14. htpolynet/core/runtime.py +774 -0
  15. htpolynet/core/topocoord.py +2044 -0
  16. htpolynet/core/topology.py +1138 -0
  17. htpolynet/cure/__init__.py +0 -0
  18. htpolynet/cure/chain.py +135 -0
  19. htpolynet/cure/curecontroller.py +746 -0
  20. htpolynet/cure/expandreactions.py +294 -0
  21. htpolynet/cure/reaction.py +280 -0
  22. htpolynet/external/__init__.py +0 -0
  23. htpolynet/external/ambertools.py +97 -0
  24. htpolynet/external/command.py +73 -0
  25. htpolynet/external/gromacs.py +308 -0
  26. htpolynet/external/slurm.py +111 -0
  27. htpolynet/external/software.py +183 -0
  28. htpolynet/geometry/__init__.py +0 -0
  29. htpolynet/geometry/bondlist.py +205 -0
  30. htpolynet/geometry/lattice.py +32 -0
  31. htpolynet/geometry/linkcell.py +210 -0
  32. htpolynet/geometry/matrix4.py +110 -0
  33. htpolynet/geometry/ring.py +274 -0
  34. htpolynet/io/__init__.py +6 -0
  35. htpolynet/io/gro.py +195 -0
  36. htpolynet/io/mol2.py +210 -0
  37. htpolynet/io/pdb.py +157 -0
  38. htpolynet/resources/README.md +20 -0
  39. htpolynet/resources/__init__.py +0 -0
  40. htpolynet/resources/cfg/DGE-PAC-hi.yaml +115 -0
  41. htpolynet/resources/cfg/DGE-PAC-lo.yaml +115 -0
  42. htpolynet/resources/cfg/FDE-DFDA-hi.yaml +116 -0
  43. htpolynet/resources/cfg/FDE-DFDA-lo.yaml +116 -0
  44. htpolynet/resources/cfg/GMASTY-hi.yaml +158 -0
  45. htpolynet/resources/cfg/GMASTY-lo.yaml +158 -0
  46. htpolynet/resources/cfg/README.md +1 -0
  47. htpolynet/resources/cfg/STY.yaml +23 -0
  48. htpolynet/resources/cfg/pMSTY-hi.yaml +101 -0
  49. htpolynet/resources/cfg/pMSTY-lo.yaml +101 -0
  50. htpolynet/resources/cfg/pSTY-hi.yaml +101 -0
  51. htpolynet/resources/cfg/pSTY-lo.yaml +101 -0
  52. htpolynet/resources/example_depot/0-liquid-styrene.sh +134 -0
  53. htpolynet/resources/example_depot/0-liquid-styrene.tgz +0 -0
  54. htpolynet/resources/example_depot/1-polystyrene.sh +202 -0
  55. htpolynet/resources/example_depot/1-polystyrene.tgz +0 -0
  56. htpolynet/resources/example_depot/2-bisgma-styrene-thermoset.sh +353 -0
  57. htpolynet/resources/example_depot/2-bisgma-styrene-thermoset.tgz +0 -0
  58. htpolynet/resources/example_depot/3-pacm-dgeba-epoxy-thermoset.sh +242 -0
  59. htpolynet/resources/example_depot/3-pacm-dgeba-epoxy-thermoset.tgz +0 -0
  60. htpolynet/resources/example_depot/4-dfda-fde-epoxy-thermoset.sh +216 -0
  61. htpolynet/resources/example_depot/4-dfda-fde-epoxy-thermoset.tgz +0 -0
  62. htpolynet/resources/mdp/README.md +1 -0
  63. htpolynet/resources/mdp/drag-min.mdp +13 -0
  64. htpolynet/resources/mdp/drag-npt.mdp +36 -0
  65. htpolynet/resources/mdp/drag-nvt.mdp +31 -0
  66. htpolynet/resources/mdp/min.mdp +14 -0
  67. htpolynet/resources/mdp/npt.mdp +37 -0
  68. htpolynet/resources/mdp/nvt.mdp +29 -0
  69. htpolynet/resources/mdp/relax-min.mdp +13 -0
  70. htpolynet/resources/mdp/relax-npt.mdp +37 -0
  71. htpolynet/resources/mdp/relax-nvt.mdp +31 -0
  72. htpolynet/resources/mdp/single-molecule-min.mdp +12 -0
  73. htpolynet/resources/mdp/single-molecule-nvt.mdp +25 -0
  74. htpolynet/resources/molecules/inputs/DFA.pdb +62 -0
  75. htpolynet/resources/molecules/inputs/DGE.mol2 +115 -0
  76. htpolynet/resources/molecules/inputs/EMB.mol2 +50 -0
  77. htpolynet/resources/molecules/inputs/FDE.pdb +72 -0
  78. htpolynet/resources/molecules/inputs/GMA.mol2 +163 -0
  79. htpolynet/resources/molecules/inputs/PAC.mol2 +91 -0
  80. htpolynet/resources/molecules/inputs/STY.mol2 +44 -0
  81. htpolynet/resources/molecules/make-monomers.sh +64 -0
  82. htpolynet/resources/molecules/pics/DFA.png +0 -0
  83. htpolynet/resources/molecules/pics/DGE.png +0 -0
  84. htpolynet/resources/molecules/pics/EMB.png +0 -0
  85. htpolynet/resources/molecules/pics/FDE.png +0 -0
  86. htpolynet/resources/molecules/pics/GMA.png +0 -0
  87. htpolynet/resources/molecules/pics/PAC.png +0 -0
  88. htpolynet/resources/molecules/pics/STY.png +0 -0
  89. htpolynet/resources/molecules/sample-inputs/DFA.pdb +62 -0
  90. htpolynet/resources/molecules/sample-inputs/DGE.mol2 +115 -0
  91. htpolynet/resources/molecules/sample-inputs/EMB.mol2 +50 -0
  92. htpolynet/resources/molecules/sample-inputs/FDE.pdb +72 -0
  93. htpolynet/resources/molecules/sample-inputs/GMA.mol2 +163 -0
  94. htpolynet/resources/molecules/sample-inputs/PAC.mol2 +91 -0
  95. htpolynet/resources/molecules/sample-inputs/STY.mol2 +44 -0
  96. htpolynet/resources/tcl/readbonds.tcl +32 -0
  97. htpolynet/resources/tcl/readgrx.tcl +46 -0
  98. htpolynet/resources/tcl/render.tcl +74 -0
  99. htpolynet/utils/__init__.py +0 -0
  100. htpolynet/utils/banner.py +34 -0
  101. htpolynet/utils/checkpoint.py +85 -0
  102. htpolynet/utils/dataframetools.py +92 -0
  103. htpolynet/utils/inputcheck.py +49 -0
  104. htpolynet/utils/logsetup.py +44 -0
  105. htpolynet/utils/stringthings.py +39 -0
  106. htpolynet-2.0.0.dist-info/METADATA +78 -0
  107. htpolynet-2.0.0.dist-info/RECORD +110 -0
  108. htpolynet-2.0.0.dist-info/WHEEL +4 -0
  109. htpolynet-2.0.0.dist-info/entry_points.txt +2 -0
  110. htpolynet-2.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,588 @@
1
+ """Provides plotting functionality.
2
+
3
+ Author: Cameron F. Abrams <cfa22@drexel.edu>
4
+ """
5
+ import logging
6
+
7
+ from datetime import datetime
8
+
9
+ import yaml
10
+
11
+ import matplotlib.cm as cm
12
+ import matplotlib.pyplot as plt
13
+ import networkx as nx
14
+ import pandas as pd
15
+
16
+ from ..analysis.utils import *
17
+ from ..external.gromacs import *
18
+ from ..utils.logsetup import setup_logging
19
+
20
+ logger=logging.getLogger(__name__)
21
+
22
+ # prevents "RuntimeError: main thread is not in main loop" tk bug
23
+ plt.switch_backend('agg')
24
+
25
+ def scatter(df,xcolumn,columns=[],outfile='plot.png',**kwargs):
26
+ """Generic scatter plot generator.
27
+
28
+ Args:
29
+ df (pd.DataFrame): dataframe containing data
30
+ xcolumn (str): name of column holding x-data
31
+ columns (list): list of y-value columns to be plotted vs. x, defaults to []
32
+ outfile (str): name of output image file, defaults to 'plot.png'
33
+ """
34
+ logging.disable(logging.DEBUG)
35
+ cmapname=kwargs.get('colormap','plasma')
36
+ size=kwargs.get('size',(8,6))
37
+ yunits=kwargs.get('yunits',None)
38
+ cmap=cm.get_cmap(cmapname)
39
+ fig,ax=plt.subplots(1,1,figsize=size)
40
+ ax.set_xlabel(xcolumn)
41
+ for n in columns:
42
+ ax.scatter(df[xcolumn],df[n],label=n)
43
+ plt.legend()
44
+ plt.savefig(outfile)
45
+ plt.close(fig)
46
+ logging.disable(logging.NOTSET)
47
+
48
+
49
+ def trace(qty,edrs,outfile='plot.png',**kwargs):
50
+ """Generates a plot of the energy-like quantity named by 'qty' vs time by reading data from the list of edr files named in 'edrs'.
51
+
52
+ Args:
53
+ qty (str): name of energy-like quantity; must conform to menu generated by 'gmx energy'
54
+ edrs (list): list of names of edr files to scan, in order
55
+ outfile (str): name of output image file, defaults to 'plot.png'
56
+
57
+ Returns:
58
+ list: the list of average values
59
+ """
60
+ # disable debug-level logging and above since matplotlib has a lot of debug statements
61
+ logging.disable(logging.DEBUG)
62
+ df=pd.DataFrame()
63
+ cmapname=kwargs.get('colormap','plasma')
64
+ size=kwargs.get('size',(8,6))
65
+ yunits=kwargs.get('yunits',None)
66
+ avgafter=kwargs.get('avgafter',0)
67
+ cmap=cm.get_cmap(cmapname)
68
+ xshift=0.0
69
+ chkpt=[]
70
+ for edr in edrs:
71
+ if not df.empty:
72
+ xshift=df.tail(1).iloc[0,0]
73
+ data=gmx_energy_trace(edr,[qty],xshift=xshift)
74
+ lastchkpt=0
75
+ if len(chkpt)>0:
76
+ lastchkpt=chkpt[-1]
77
+ chkpt.append(data.shape[0]+lastchkpt)
78
+ df=pd.concat((df,data),ignore_index=True)
79
+ fig,ax=plt.subplots(1,1,figsize=size)
80
+ nseg=len(chkpt)
81
+ beg=0
82
+ avg=[]
83
+ for c in df.columns[1:]:
84
+ for seg in range(nseg):
85
+ ax.plot(df.iloc[beg:chkpt[seg],0],df[c].iloc[beg:chkpt[seg]],label=(c if seg==0 else None),color=cmap(seg/nseg))
86
+ beg=chkpt[seg]
87
+ if 'avgafter' in kwargs:
88
+ if avgafter>0:
89
+ pass
90
+ else:
91
+ avgafter=df['time(ps)'].iloc[-1]/2
92
+ sdf=df[df['time(ps)']>avgafter]
93
+ avg.append(sdf[c].mean())
94
+ ax.plot(df.iloc[:,0],[avg]*df.shape[0],'k-',alpha=0.3,label=f'{avg:0.2f}')
95
+ else:
96
+ avg.append(df[c].mean())
97
+ if not yunits:
98
+ plt.ylabel(qty)
99
+ else:
100
+ plt.ylabel(f'{qty} ({yunits})')
101
+ plt.xlabel('time(ps)')
102
+ plt.legend()
103
+ plt.savefig(outfile)
104
+ plt.close(fig)
105
+ # re-establish previous logging level
106
+ logging.disable(logging.NOTSET)
107
+ return avg
108
+
109
+ def multi_trace(dfL,xnames,ynames,labels=[],xlabel='time [ps]',ylabel='',outfile='plot.png',**kwargs):
110
+ """Generates a plot of each y vs x in df.
111
+
112
+ Args:
113
+ dfL (list of pandas.DataFrame): list of dataframes, one per trace
114
+ xnames (list): list of x-column names
115
+ ynames (list): list of y-column names, parallel to xnames
116
+ outfile (str): name of output image file, defaults to 'plot.png'
117
+ """
118
+ # disable debug-level logging and above since matplotlib has a lot of debug statements
119
+ default_units={'Temperature':'K','Pressure':'bar','Density':'kg/m^3','Potential':'kJ/mol'}
120
+ units=kwargs.get('units',default_units)
121
+ logging.disable(logging.DEBUG)
122
+ size=kwargs.get('size',(16,4))
123
+ legend=kwargs.get('legend',True)
124
+ fig,ax=plt.subplots(1,1,figsize=size)
125
+ plt.xlabel(xlabel)
126
+ plt.ylabel(ylabel)
127
+ cmapname=kwargs.get('colormap','plasma')
128
+ cmap=cm.get_cmap(cmapname)
129
+
130
+ ndatasets=len(xnames)
131
+ assert ndatasets==len(ynames)
132
+
133
+ for i,(df,x,y,l) in enumerate(zip(dfL,xnames,ynames,labels)):
134
+ ax.plot(df[x],df[y],label=l,color=cmap(i/ndatasets))
135
+
136
+ if legend:
137
+ plt.legend()
138
+ plt.savefig(outfile)
139
+ plt.close(fig)
140
+ # re-establish previous logging level
141
+ logging.disable(logging.NOTSET)
142
+
143
+
144
+ def global_trace(df,names,outfile='plot.png',transition_times=[],markers=[],interval_labels=[],y2names=[],**kwargs):
145
+ """Generates custom-formatted multiplots of energy-like quantities named in 'names' in the input dataframe df.
146
+
147
+ Args:
148
+ df (pd.DataFrame): pandas dataframe containing all data
149
+ names (list): list of quantity names (Density, Temperature, etc)
150
+ outfile (str): name of output image file, defaults to 'plot.png'
151
+ transition_times (list): time values at which vertical lines are drawn, defaults to []
152
+ markers (list): time values at which transitions are marked, defaults to []
153
+ interval_labels (list): list of labels of intervals defined by markers, defaults to []
154
+ y2names (list): names of quantities to be plotted on a secondary y axis, defaults to []
155
+ """
156
+ # disable debug-level logging and above since matplotlib has a lot of debug statements
157
+ default_units={'Temperature':'K','Pressure':'bar','Density':'kg/m^3','Potential':'kJ/mol'}
158
+ units=kwargs.get('units',default_units)
159
+ logging.disable(logging.DEBUG)
160
+ size=kwargs.get('size',(16,4*len(names)))
161
+ legend=kwargs.get('legend',False)
162
+ fig,ax=plt.subplots(len(names),1,figsize=size)
163
+ plt.xlabel('time(ps)')
164
+ cmapname=kwargs.get('colormap','plasma')
165
+ # yunits=kwargs.get('yunits',None)
166
+ cmap=cm.get_cmap(cmapname)
167
+ # print(f'in global_trace:\n{df.head().to_string()}')
168
+
169
+
170
+ interval_times=[]
171
+ if interval_labels:
172
+ for i in range(1,len(transition_times)):
173
+ interval_times.append((transition_times[i]+transition_times[i-1])/2)
174
+ for l,t in zip(interval_labels,interval_times):
175
+ logger.info(f'{t} {l}')
176
+ assert len(interval_labels)==len(interval_times)
177
+ L,R=-1,-1
178
+ if len(markers)>1:
179
+ L,R=markers[0],markers[-1]
180
+ in_tt=[x for x in transition_times if L<x<R]
181
+ marked_df=df[(df['time(ps)']>L)&(df['time(ps)']<R)]
182
+ fig,ax=plt.subplots(len(names)*2,1,figsize=size)
183
+ for i,colname in enumerate(names):
184
+ out_ax=ax[0] if len(names)==1 else ax[i*2]
185
+ in_ax=ax[1] if len(names)==1 else ax[i*2+1]
186
+ out_ax.plot(df['time(ps)'],df[colname],label=colname)
187
+ ylabel=colname
188
+ if ylabel in units: ylabel+=f' ({units[ylabel]})'
189
+ out_ax.set_ylabel(ylabel)
190
+ out_ax.set_xlabel('time(ps)')
191
+ if len(y2names)>i:
192
+ out_ax2=out_ax.twinx()
193
+ out_ax2.plot(df['time(ps)'],df[y2names[i]],label=y2names[i],color='black')
194
+ ylabel=y2names[i]
195
+ if ylabel in units: ylabel+=f' ({units[ylabel]})'
196
+ out_ax2.set_ylabel(ylabel)
197
+ out_ax2.set_xlabel('time(ps)')
198
+ if len(transition_times)>0:
199
+ colors=[cmap(i/len(transition_times)) for i in range(len(transition_times))]
200
+ ylim=out_ax.get_ylim()
201
+ out_ax.vlines(transition_times,ylim[0],ylim[1],color=colors,linewidth=0.75,alpha=0.5)
202
+ # for x,l in zip(interval_times,interval_labels):
203
+ # out_ax.text(x,0.9*ylim[1],l,fontsize=8)
204
+ in_ax.plot(marked_df['time(ps)'],marked_df[colname],label=colname)
205
+ ylabel=colname
206
+ if ylabel in units: ylabel+=f' ({units[ylabel]})'
207
+ in_ax.set_xlabel('time(ps)')
208
+ in_ax.set_yabel(ylabel)
209
+ if len(y2names)>i:
210
+ in_ax2=in_ax.twinx()
211
+ in_ax2.plot(marked_df['time(ps)'],marked_df[y2names[i]],label=y2names[i],color='black')
212
+ ylabel=y2names[i]
213
+ if ylabel in units: ylabel+=f' ({units[ylabel]})'
214
+ in_ax2.set_ylabel(ylabel)
215
+ in_ax2.set_xlabel('time(ps)')
216
+ if len(transition_times)>0:
217
+ colors=[cmap(i/len(transition_times)) for i in range(len(transition_times))]
218
+ ylim=in_ax.get_ylim()
219
+ in_ax.vlines(in_tt,ylim[0],ylim[1],color=colors,linewidth=0.75,alpha=0.5)
220
+ # for x,l in zip(interval_times,interval_labels):
221
+ # if L<x<R:
222
+ # out_ax.text(x,0.9*ylim[1],l,fontsize=8)
223
+ else:
224
+ fig,ax=plt.subplots(len(names),1,figsize=size)
225
+ for i,colname in enumerate(names):
226
+ the_ax=ax if len(names)==1 else ax[i]
227
+ the_ax.plot(df['time(ps)'],df[colname],label=colname)
228
+ ylabel=colname
229
+ if ylabel in units: ylabel+=f' ({units[ylabel]})'
230
+ the_ax.set_ylabel(ylabel)
231
+ the_ax.set_xlabel('time(ps)')
232
+ if len(y2names)>i:
233
+ the_ax2=the_ax.twinx()
234
+ the_ax2.plot(df['time(ps)'],df[y2names[i]],label=y2names[i],color='black')
235
+ ylabel=y2names[i]
236
+ if ylabel in units: ylabel+=f' ({units[ylabel]})'
237
+ the_ax2.set_ylabel(ylabel)
238
+ if len(transition_times)>0:
239
+ colors=[cmap(i/len(transition_times)) for i in range(len(transition_times))]
240
+ ylim=the_ax.get_ylim()
241
+ the_ax.vlines(transition_times,ylim[0],ylim[1],color=colors,linewidth=0.5,alpha=0.5)
242
+
243
+ # plt.xlabel('time(ps)')
244
+ if legend:
245
+ plt.legend()
246
+ plt.savefig(outfile)
247
+ plt.close(fig)
248
+ # re-establish previous logging level
249
+ logging.disable(logging.NOTSET)
250
+
251
+ def network_graph(G,filename,**kwargs):
252
+ """Draws a custom formatted network plot from graph G.
253
+
254
+ Args:
255
+ G (nx.Graph or nx.DiGraph): a graph from networkx
256
+ filename (str): name of output image filename
257
+ """
258
+ logging.disable(logging.DEBUG)
259
+ arrows=kwargs.get('arrows',False)
260
+ figsize=kwargs.get('figsize',(32,32))
261
+ node_size=kwargs.get('node_size',200)
262
+ with_labels=kwargs.get('with_labels',False)
263
+ cmap=cm.get_cmap('seismic')
264
+ fig,ax=plt.subplots(1,1,figsize=figsize)
265
+ ax.axis('off')
266
+ molnames=list(set([n.get('molecule_name','anonymous') for k,n in G.nodes.items()]))
267
+ nmolname=len(molnames)
268
+ cx=[]
269
+ for n in G.nodes.values():
270
+ idx=molnames.index(n.get('molecule_name','anonymous'))
271
+ cx.append((float(idx)+0.25)/(nmolname+1))
272
+ nx.draw_networkx(G,ax=ax,arrows=arrows,node_size=node_size,node_color=cx,cmap=cmap,with_labels=with_labels)
273
+ plt.savefig(filename)
274
+ plt.close(fig)
275
+ logging.disable(logging.NOTSET)
276
+
277
+ # below are representive diagnostic output lines to establish extraction patterns
278
+ _template_1='2022-08-11 17:40:36,969 HTPolyNet.runtime.my_logger INFO> ********* Connect-Update-Relax-Equilibrate (CURE) begins **********'
279
+ _template_1_token_idx=[2,3,5,7]
280
+ _template_2='2022-09-03 19:32:46,830 HTPolyNet.curecontroller.do_iter INFO> Iteration 1 current conversion 0.283 or 1082 bonds'
281
+ _template_2_token_idx=[2,3,6,7]
282
+ _template_2_data_idx={'iter':(int,5),'conv':(float,8),'nbonds':(int,10)}
283
+
284
+ def _token_match(l,template,pat_idx):
285
+ """Returns True if tokens indexed by pat_idx in the space-split l and template match.
286
+
287
+ Args:
288
+ l (str): probe string
289
+ template (str): template string
290
+ pat_idx (list): list of token indices
291
+
292
+ Returns:
293
+ bool: True if tokens in l match those in template
294
+ """
295
+ if len(l.split())!=len(template.split()): return
296
+ return all([l.split()[t]==template.split()[t] for t in pat_idx])
297
+
298
+ def _parse_data(dat,l,idx_dict):
299
+ """Parses data in a matching line.
300
+
301
+ Args:
302
+ dat (pd.DataFrame): pandas dataframe
303
+ l (str): line
304
+ idx_dict (dict): dictionary of column-name:(type-converter,token-index)
305
+ """
306
+ tok=l.split()
307
+ for k,v in idx_dict.items():
308
+ conv,s=v
309
+ dat[k].append(conv(tok[s]))
310
+
311
+ def diagnostics_graphs(logfiles,filename,**kwargs):
312
+ """Extracts selected data from the diagnostic output and generates plots.
313
+
314
+ Args:
315
+ logfiles (list): list of names of diagnostic log files to treat in parallel
316
+ filename (str): name of output image file
317
+ """
318
+ xmax=kwargs.get('xmax',-1)
319
+ figsize=kwargs.get('figsize',(12,6))
320
+ logging.disable(logging.DEBUG)
321
+ df:dict[pd.DataFrame]={}
322
+ for logfile in logfiles:
323
+ bn,ex=os.path.splitext(logfile)
324
+ with open(logfile,'r') as f:
325
+ lines=f.read().split('\n')
326
+ logger.info(f'read {len(lines)} lines from {logfile}')
327
+ data={}
328
+ data['time']=[]
329
+ data['iter']=[]
330
+ data['conv']=[]
331
+ data['nbonds']=[]
332
+ counter=0
333
+ for l in lines:
334
+ if _token_match(l,_template_1,_template_1_token_idx):
335
+ # print('you should only see this once')
336
+ counter+=1
337
+ assert not counter>1
338
+ data['time'].append(datetime.strptime(' '.join(l.split()[0:2]),'%Y-%m-%d %H:%M:%S,%f'))
339
+ data['iter'].append(0)
340
+ data['conv'].append(0.0)
341
+ data['nbonds'].append(0)
342
+ elif _token_match(l,_template_2,_template_2_token_idx):
343
+ data['time'].append(datetime.strptime(' '.join(l.split()[0:2]),'%Y-%m-%d %H:%M:%S,%f'))
344
+ # print('data tok',f'{l.split()}')
345
+ _parse_data(data,l,_template_2_data_idx)
346
+ # print('data',f'{data}')
347
+ df[logfile]=pd.DataFrame(data)
348
+ time_idx=list(df[logfile].columns).index('time')
349
+ df[logfile]['elapsed']=(df[logfile]['time']-df[logfile].iloc[0,time_idx]).astype(int)/1.e9/3600.0
350
+ df[logfile].to_csv(f'{bn}.csv',index=False,sep=' ',header=True)
351
+ fig,ax=plt.subplots(1,2,sharex=True,figsize=figsize)
352
+ ax[0].set_ylim([0,1])
353
+ ax[0].set_xlabel('runtime (h)')
354
+ ax[0].set_ylabel('conversion')
355
+ ax[1].set_xlabel('runtime (h)')
356
+ ax[1].set_ylabel('iteration')
357
+ if xmax>-1:
358
+ ax[0].set_xlim([0,xmax])
359
+ for logfile in logfiles:
360
+ ax[0].plot(df[logfile]['elapsed'],df[logfile]['conv'],label=logfile)
361
+ ax[1].plot(df[logfile]['elapsed'],df[logfile].index+1,label=logfile)
362
+ plt.legend()
363
+ plt.savefig(filename)
364
+ plt.close(fig)
365
+ logging.disable(logging.NOTSET)
366
+
367
+ def init_molecule_graph(proj_dir):
368
+ """Creates and initializes an inter-molecular graph to show network connectivity.
369
+
370
+ Args:
371
+ proj_dir (str): name of project directory
372
+
373
+ Returns:
374
+ networkx.Graph: a nodes-only Graph enumerating all molecules; this will be further processed elsewhere to add connectivity information
375
+ """
376
+ gro=os.path.join(proj_dir,'systems/init/init.gro')
377
+ top=os.path.join(proj_dir,'systems/init/init.top')
378
+ grx=os.path.join(proj_dir,'systems/init/init.grx')
379
+ TC=TopoCoord(grofilename=gro,topfilename=top,grxfilename=grx)
380
+ G=nx.Graph()
381
+ adf=TC.Coordinates.A
382
+ mm=set(zip(adf['molecule'],adf['molecule_name']))
383
+ for mx,mn in mm:
384
+ G.add_node(mx,molecule_name=mn)
385
+ return G
386
+
387
+ def plots(args):
388
+ """Handles the plots subcommand.
389
+
390
+ Args:
391
+ args (argparse.Namespace): command-line arguments
392
+ """
393
+ setup_logging(args.loglevel, no_banner=args.no_banner)
394
+
395
+ if args.source=='build':
396
+ build_plots(args)
397
+ elif args.source=='diag':
398
+ diag_plots(args)
399
+ elif args.source=='post':
400
+ post_plots(args)
401
+ else:
402
+ logger.error(f'Source {args.source} is not recognized.')
403
+
404
+ def diag_plots(args):
405
+ diags=args.diags
406
+ plotfile=args.plotfile
407
+ if not plotfile:
408
+ plotfile='cure_info.png'
409
+ if len(diags)>0:
410
+ diagnostics_graphs(diags,plotfile)
411
+
412
+ def build_plots(args):
413
+ GmxNames={'t':'Temperature','d':'Density','p':'Potential'}
414
+ plot_types=args.buildplot
415
+ trace_types=[]
416
+ if 't' in plot_types:
417
+ trace_types=[GmxNames[i] for i in args.traces]
418
+ for p in args.proj:
419
+ if trace_types:
420
+ df,transition_times,cure_markers,interval_labels=density_evolution(p)
421
+ global_trace(df,trace_types,os.path.join(p,'buildtraces.png'),transition_times=transition_times,markers=[],interval_labels=interval_labels,y2names=['nbonds','nbonds'],legend=True)
422
+ df.to_csv(os.path.join(p,'buildtraces.csv'),index=False,header=True,float_format='{:.3f}'.format)
423
+ if any([x in plot_types for x in 'gnc']):
424
+ G=init_molecule_graph(p)
425
+ n=1
426
+ while os.path.exists(os.path.join(p,f'systems/iter-{n}/2-cure_update-bonds.csv')):
427
+ logger.info(f'iter-{n}/2-cure_update-bonds.csv')
428
+ g=graph_from_bondsfile(os.path.join(p,f'systems/iter-{n}/2-cure_update-bonds.csv'))
429
+ G=nx.compose(G,g)
430
+ if 'g' in plot_types:
431
+ network_graph(G,os.path.join(p,f'plots/iter-{n}-graph.png'))
432
+ n+=1
433
+ if 'g' in plot_types: network_graph(G,os.path.join(p,'graph.png'))
434
+ if 'c' in plot_types:
435
+ clu=clusters(G)
436
+ clu.to_csv(os.path.join(p,'clusters.csv'),sep=' ',header=True,index=False)
437
+ logger.info(f'{os.path.join(p,"clusters.csv")} created.')
438
+ # cluster_plot(clu,os.path.join(p,"clusters.png"))
439
+ if 'n' in plot_types:
440
+ am=mwbxl(G)
441
+ logger.info(f'Avg homo-N between xlinks: {np.average(am["n"],weights=am["counts"]):.2f}')
442
+ am.to_csv(os.path.join(p,'dist_bw_xlinks.csv'),sep=' ',index=False,header=True)
443
+ logger.info(f'{os.path.join(p,"dist_bw_xlinks.csv")} created.')
444
+ # dist_bw_xlinks_plot(am,os.path.join(p,"dist_bw_xlinks.png"))
445
+
446
+ def do_tg_plots(phases,projdirs,outfile='tg.png',save_data='data.csv',n_points=[10,20]):
447
+ res={}
448
+ means={}
449
+ stds={}
450
+ rate=[]
451
+ Tgs=[]
452
+ nproj=len(projdirs)
453
+ for i,phase in enumerate(phases):
454
+ p=phase['ladder']
455
+ rate.append(p['deltaT']/(p['ps_per_rise']+p['ps_per_run']))
456
+ res[i]=[]
457
+ for d in projdirs:
458
+ df=pd.read_csv(os.path.join(d,p['subdir'],'ladder.csv'),index_col=None,header=0)
459
+ t0=p['warmup_ps']
460
+ ps_per_step=p['ps_per_rise']+p['ps_per_run']
461
+ final_ps=df['time(ps)'].iloc[-1]
462
+ curr_ps=t0
463
+ T=[]
464
+ rho=[]
465
+ V=[]
466
+ Tstd=[]
467
+ rhostd=[]
468
+ Vstd=[]
469
+ while curr_ps<final_ps:
470
+ ll=curr_ps+p['ps_per_rise']+0.5*p['ps_per_run']
471
+ ul=ll+0.5*p['ps_per_run']
472
+ tdf=df[(df['time(ps)']>=ll)&(df['time(ps)']<=ul)]
473
+ T.append(tdf['Temperature'].mean())
474
+ rho.append(tdf['Density'].mean())
475
+ V.append(tdf['Volume'].mean())
476
+ Tstd.append(tdf['Temperature'].std())
477
+ rhostd.append(tdf['Density'].std())
478
+ Vstd.append(tdf['Volume'].std())
479
+ curr_ps+=ps_per_step
480
+
481
+ res[i].append(pd.DataFrame({'Temperature':T,'Volume':V,'Density':rho,'Temperature-std':Tstd,'Volume-std':Vstd,'Density-std':rhostd}))
482
+
483
+ df_concat0 = pd.concat(res[i], axis=1)
484
+ means[i]=df_concat0.stack().groupby(level=[0,1]).mean().unstack()
485
+ stds[i]=df_concat0.stack().groupby(level=[0,1]).std().unstack()
486
+ means[i]['Volume-std']=stds[i]['Volume']
487
+ means[i]['Density-std']=stds[i]['Density']
488
+
489
+
490
+ fig,ax=plt.subplots(1,2,figsize=(10,6),sharex=True,sharey=True)
491
+ for i,v in enumerate(means.keys()):
492
+ m=means[v]
493
+ std=stds[v]
494
+ if np.isnan(np.sum(std['Density'])):
495
+ std['Density']=np.zeros(len(std['Density']))
496
+ m.sort_values('Temperature',axis=0,inplace=True)
497
+ ax[i].set_xlabel('Temperature [K]')
498
+ ax[i].set_ylabel('Density [kg/m$^3$]')
499
+ if nproj<2:
500
+ ax[i].scatter(m['Temperature'],m['Density'])
501
+ else:
502
+ ax[i].errorbar(m['Temperature'],m['Density'],std['Density'])
503
+ Tg,c,h=compute_tg(m['Temperature'],m['Density'],n_points=n_points)
504
+ Tgs.append(Tg)
505
+ if Tg!=-1:
506
+ ax[i].plot(m['Temperature'],c[0]*m['Temperature']+c[1],color='blue',alpha=0.5)
507
+ ax[i].plot(m['Temperature'],h[0]*m['Temperature']+h[1],color='red',alpha=0.5)
508
+ ax[i].scatter([Tg],[c[0]*Tg+c[1]],marker='o',color='black')
509
+ ax[i].text(Tg,c[0]*Tg+c[1],f' {Tg:.2f} K',verticalalignment='top')
510
+ means[v]['glassy-line-Density']=c[0]*m['Temperature']+c[1]
511
+ means[v]['rubbery-line-Density']=h[0]*m['Temperature']+h[1]
512
+ means[v].to_csv(f'ladder{v}-{save_data}',index=False,header=True,sep=' ')
513
+ logger.info(f'ladder{v}-{save_data} created.')
514
+ plt.savefig(outfile,bbox_inches='tight')
515
+ plt.close(fig)
516
+ hTg=Tgs[0]
517
+ cTg=Tgs[1]
518
+ logger.info(f'{outfile} created.')
519
+ logger.info(f'heating Tg = {hTg:.2f} K ({(hTg-273.15):.2f} C) at {rate[0]:.5f} K/ps ({rate[0]*1.e12:.3e} K/s)')
520
+ logger.info(f'cooling Tg = {cTg:.2f} K ({(cTg-273.15):.2f} C) at {rate[1]:.5f} K/ps ({rate[1]*1.e12:.3e} K/s)')
521
+
522
+ def do_E_plots(phases,projdirs,outfile='e.png',fit_domain=[10,200],save_data='E.csv'):
523
+ MPa_per_bar=1.e-1
524
+ # average over replicas and directions (here, phases)
525
+ all_stress_strains=[]
526
+ for p in phases:
527
+ params=p['deform']
528
+ dir=params['direction']
529
+ # Box-X-strain,Pres-XX-stress
530
+ strain_name=f'Box-{dir.upper()}-strain'
531
+ stress_name=f'Pres-{dir.upper()}{dir.upper()}-stress'
532
+ for d in projdirs:
533
+ df=pd.read_csv(os.path.join(d,params['subdir'],f'deform-{dir}.csv'),index_col=None,header=0)
534
+ all_stress_strains.append(pd.DataFrame({'strain':df[strain_name],'stress':(df[stress_name]*MPa_per_bar)}))
535
+ df_concat=pd.concat(all_stress_strains,axis=1)
536
+ mean_stress_strains=df_concat.stack().groupby(level=[0,1]).mean().unstack()
537
+ stds_stress_strains=df_concat.stack().groupby(level=[0,1]).std().unstack()
538
+ mean_stress_strains['stress-std']=stds_stress_strains['stress']
539
+ mean_stress_strains.to_csv(save_data,header=True,index=False,sep=' ')
540
+ fig,ax=plt.subplots(1,1,figsize=(8,6))
541
+
542
+ ax.set_ylabel('Stress, [MPa]')
543
+ ax.set_xlabel('Strain [*]')
544
+ ax.errorbar(mean_stress_strains['strain'],mean_stress_strains['stress'],stds_stress_strains['stress'],alpha=0.2)
545
+ ax.plot(mean_stress_strains['strain'],mean_stress_strains['stress'])
546
+ E,R2=compute_E(mean_stress_strains['strain'],mean_stress_strains['stress'],fit_domain=fit_domain)
547
+ fitline=E*mean_stress_strains['strain']
548
+ half_domain=int(mean_stress_strains['strain'].shape[0]/2)
549
+ line_domain=[0,fit_domain[1] if fit_domain[1]>half_domain else half_domain]
550
+ X=np.array(mean_stress_strains['strain'])[line_domain[0]:line_domain[1]]
551
+ Y=np.array(fitline)[line_domain[0]:line_domain[1]]
552
+ ax.plot(X,Y,'k--',alpha=0.7)
553
+ plt.savefig(outfile,bbox_inches='tight')
554
+ plt.close(fig)
555
+ logger.info(f'{outfile} and {save_data} created. E = {E/1000.0:.3f} GPa (R^2 {R2:.3f})')
556
+
557
+ def post_plots(args):
558
+ n_points=args.n_points
559
+ phases=[]
560
+ # print(args.cfg)
561
+ for c in args.cfg:
562
+ with open(c,'r') as f:
563
+ phases+=yaml.safe_load(f)
564
+ phasenames=[list(x.keys())[0] for x in phases]
565
+ # print(phasenames)
566
+ mdf=[]
567
+ for p in args.proj:
568
+ if 'anneal' in phasenames and 'equilibrate' in phasenames:
569
+ mdf.append(postsim_density_evolution(p))
570
+ if mdf:
571
+ multi_trace(mdf,xnames=['time(ps)']*len(mdf),ynames=['Density']*len(mdf),labels=args.proj,ylabel='Density [kg/m$^3$]',outfile='anneal-equil-density.png')
572
+ logger.info('-'.join(phasenames)+'-density.png created.')
573
+ m=[]
574
+ for d in mdf:
575
+ seg=d.iloc[int(0.9*d.shape[0]):]
576
+ logger.debug(f'averaging {seg.shape[0]} final density values out of {d.shape[0]}')
577
+ m.append(seg['Density'].mean())
578
+ m=np.array(m)
579
+ logger.info(f'mean density {m.mean():.0f} kg/m^3 ({m.mean()/1000.0:.3f} g/cc)')
580
+
581
+ ladder_phases=[idx for idx, value in enumerate(phasenames) if value == 'ladder']
582
+ # print(ladder_phases)
583
+ if len(ladder_phases)>0: # perform Tg calculations on each phase
584
+ do_tg_plots([phases[i] for i in ladder_phases],args.proj,n_points=n_points)
585
+
586
+ deform_phases=[idx for idx,value in enumerate(phasenames) if value == 'deform']
587
+ if len(deform_phases)>0:
588
+ do_E_plots([phases[i] for i in deform_phases],args.proj)