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.
- htpolynet/__init__.py +2 -0
- htpolynet/analysis/__init__.py +0 -0
- htpolynet/analysis/analyze.py +256 -0
- htpolynet/analysis/plot.py +588 -0
- htpolynet/analysis/postsim.py +444 -0
- htpolynet/analysis/utils.py +399 -0
- htpolynet/cli.py +488 -0
- htpolynet/core/__init__.py +0 -0
- htpolynet/core/bondtemplate.py +112 -0
- htpolynet/core/configuration.py +83 -0
- htpolynet/core/coordinates.py +644 -0
- htpolynet/core/molecule.py +998 -0
- htpolynet/core/projectfilesystem.py +616 -0
- htpolynet/core/runtime.py +774 -0
- htpolynet/core/topocoord.py +2044 -0
- htpolynet/core/topology.py +1138 -0
- htpolynet/cure/__init__.py +0 -0
- htpolynet/cure/chain.py +135 -0
- htpolynet/cure/curecontroller.py +746 -0
- htpolynet/cure/expandreactions.py +294 -0
- htpolynet/cure/reaction.py +280 -0
- htpolynet/external/__init__.py +0 -0
- htpolynet/external/ambertools.py +97 -0
- htpolynet/external/command.py +73 -0
- htpolynet/external/gromacs.py +308 -0
- htpolynet/external/slurm.py +111 -0
- htpolynet/external/software.py +183 -0
- htpolynet/geometry/__init__.py +0 -0
- htpolynet/geometry/bondlist.py +205 -0
- htpolynet/geometry/lattice.py +32 -0
- htpolynet/geometry/linkcell.py +210 -0
- htpolynet/geometry/matrix4.py +110 -0
- htpolynet/geometry/ring.py +274 -0
- htpolynet/io/__init__.py +6 -0
- htpolynet/io/gro.py +195 -0
- htpolynet/io/mol2.py +210 -0
- htpolynet/io/pdb.py +157 -0
- htpolynet/resources/README.md +20 -0
- htpolynet/resources/__init__.py +0 -0
- htpolynet/resources/cfg/DGE-PAC-hi.yaml +115 -0
- htpolynet/resources/cfg/DGE-PAC-lo.yaml +115 -0
- htpolynet/resources/cfg/FDE-DFDA-hi.yaml +116 -0
- htpolynet/resources/cfg/FDE-DFDA-lo.yaml +116 -0
- htpolynet/resources/cfg/GMASTY-hi.yaml +158 -0
- htpolynet/resources/cfg/GMASTY-lo.yaml +158 -0
- htpolynet/resources/cfg/README.md +1 -0
- htpolynet/resources/cfg/STY.yaml +23 -0
- htpolynet/resources/cfg/pMSTY-hi.yaml +101 -0
- htpolynet/resources/cfg/pMSTY-lo.yaml +101 -0
- htpolynet/resources/cfg/pSTY-hi.yaml +101 -0
- htpolynet/resources/cfg/pSTY-lo.yaml +101 -0
- htpolynet/resources/example_depot/0-liquid-styrene.sh +134 -0
- htpolynet/resources/example_depot/0-liquid-styrene.tgz +0 -0
- htpolynet/resources/example_depot/1-polystyrene.sh +202 -0
- htpolynet/resources/example_depot/1-polystyrene.tgz +0 -0
- htpolynet/resources/example_depot/2-bisgma-styrene-thermoset.sh +353 -0
- htpolynet/resources/example_depot/2-bisgma-styrene-thermoset.tgz +0 -0
- htpolynet/resources/example_depot/3-pacm-dgeba-epoxy-thermoset.sh +242 -0
- htpolynet/resources/example_depot/3-pacm-dgeba-epoxy-thermoset.tgz +0 -0
- htpolynet/resources/example_depot/4-dfda-fde-epoxy-thermoset.sh +216 -0
- htpolynet/resources/example_depot/4-dfda-fde-epoxy-thermoset.tgz +0 -0
- htpolynet/resources/mdp/README.md +1 -0
- htpolynet/resources/mdp/drag-min.mdp +13 -0
- htpolynet/resources/mdp/drag-npt.mdp +36 -0
- htpolynet/resources/mdp/drag-nvt.mdp +31 -0
- htpolynet/resources/mdp/min.mdp +14 -0
- htpolynet/resources/mdp/npt.mdp +37 -0
- htpolynet/resources/mdp/nvt.mdp +29 -0
- htpolynet/resources/mdp/relax-min.mdp +13 -0
- htpolynet/resources/mdp/relax-npt.mdp +37 -0
- htpolynet/resources/mdp/relax-nvt.mdp +31 -0
- htpolynet/resources/mdp/single-molecule-min.mdp +12 -0
- htpolynet/resources/mdp/single-molecule-nvt.mdp +25 -0
- htpolynet/resources/molecules/inputs/DFA.pdb +62 -0
- htpolynet/resources/molecules/inputs/DGE.mol2 +115 -0
- htpolynet/resources/molecules/inputs/EMB.mol2 +50 -0
- htpolynet/resources/molecules/inputs/FDE.pdb +72 -0
- htpolynet/resources/molecules/inputs/GMA.mol2 +163 -0
- htpolynet/resources/molecules/inputs/PAC.mol2 +91 -0
- htpolynet/resources/molecules/inputs/STY.mol2 +44 -0
- htpolynet/resources/molecules/make-monomers.sh +64 -0
- htpolynet/resources/molecules/pics/DFA.png +0 -0
- htpolynet/resources/molecules/pics/DGE.png +0 -0
- htpolynet/resources/molecules/pics/EMB.png +0 -0
- htpolynet/resources/molecules/pics/FDE.png +0 -0
- htpolynet/resources/molecules/pics/GMA.png +0 -0
- htpolynet/resources/molecules/pics/PAC.png +0 -0
- htpolynet/resources/molecules/pics/STY.png +0 -0
- htpolynet/resources/molecules/sample-inputs/DFA.pdb +62 -0
- htpolynet/resources/molecules/sample-inputs/DGE.mol2 +115 -0
- htpolynet/resources/molecules/sample-inputs/EMB.mol2 +50 -0
- htpolynet/resources/molecules/sample-inputs/FDE.pdb +72 -0
- htpolynet/resources/molecules/sample-inputs/GMA.mol2 +163 -0
- htpolynet/resources/molecules/sample-inputs/PAC.mol2 +91 -0
- htpolynet/resources/molecules/sample-inputs/STY.mol2 +44 -0
- htpolynet/resources/tcl/readbonds.tcl +32 -0
- htpolynet/resources/tcl/readgrx.tcl +46 -0
- htpolynet/resources/tcl/render.tcl +74 -0
- htpolynet/utils/__init__.py +0 -0
- htpolynet/utils/banner.py +34 -0
- htpolynet/utils/checkpoint.py +85 -0
- htpolynet/utils/dataframetools.py +92 -0
- htpolynet/utils/inputcheck.py +49 -0
- htpolynet/utils/logsetup.py +44 -0
- htpolynet/utils/stringthings.py +39 -0
- htpolynet-2.0.0.dist-info/METADATA +78 -0
- htpolynet-2.0.0.dist-info/RECORD +110 -0
- htpolynet-2.0.0.dist-info/WHEEL +4 -0
- htpolynet-2.0.0.dist-info/entry_points.txt +2 -0
- htpolynet-2.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
"""Various utility methods for plotting and postprocessing.
|
|
2
|
+
|
|
3
|
+
Author: Cameron F. Abrams <cfa22@drexel.edu>
|
|
4
|
+
"""
|
|
5
|
+
import logging
|
|
6
|
+
import os
|
|
7
|
+
|
|
8
|
+
import networkx as nx
|
|
9
|
+
import numpy as np
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
from scipy.constants import physical_constants
|
|
13
|
+
from scipy.optimize import curve_fit
|
|
14
|
+
|
|
15
|
+
from ..core.coordinates import Coordinates
|
|
16
|
+
from ..core.topocoord import TopoCoord
|
|
17
|
+
from ..external.gromacs import gmx_energy_trace
|
|
18
|
+
|
|
19
|
+
logger=logging.getLogger(__name__)
|
|
20
|
+
|
|
21
|
+
def density_from_gro(gro,mollib='./lib/molecules/parameterized',units='SI'):
|
|
22
|
+
"""Computes density from a Gromacs gro file.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
gro (str): name of gro file
|
|
26
|
+
mollib (str): location of parameterized molecular templates, defaults to './lib/molecules/parameterized'
|
|
27
|
+
units (str): string indicating unit system, defaults to 'SI'
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
float: density
|
|
31
|
+
"""
|
|
32
|
+
C=Coordinates.read_gro(gro)
|
|
33
|
+
resnames=list(set(C.A['resName']))
|
|
34
|
+
templates={x:TopoCoord.from_top_gro(os.path.join(mollib,f'{x}.top'),os.path.join(mollib,f'{x}.gro')) for x in resnames}
|
|
35
|
+
mass=0.0
|
|
36
|
+
counts={}
|
|
37
|
+
for nm,T in templates.items():
|
|
38
|
+
if not nm in counts:
|
|
39
|
+
counts[nm]={}
|
|
40
|
+
for an in T.Topology.D['atoms']['atom']:
|
|
41
|
+
counts[nm][an]=0
|
|
42
|
+
these=C.A[C.A['resName']==nm]['atomName']
|
|
43
|
+
for n in these:
|
|
44
|
+
counts[nm][n]+=1
|
|
45
|
+
for nm,adict in counts.items():
|
|
46
|
+
T=templates[nm].Topology.D['atoms']
|
|
47
|
+
for aname,c in adict.items():
|
|
48
|
+
mass+=c*T[T['atom']==aname]['mass'].values[0]
|
|
49
|
+
volume=np.prod(C.box.diagonal())
|
|
50
|
+
fac=1.0
|
|
51
|
+
if units=='SI':
|
|
52
|
+
mfac=physical_constants['atomic mass constant'][0]
|
|
53
|
+
lfac=1.e-9
|
|
54
|
+
vfac=lfac**3
|
|
55
|
+
fac=mfac/vfac
|
|
56
|
+
return fac*mass/volume
|
|
57
|
+
|
|
58
|
+
""" These globals are used in traversing a project directory to extract data from edr files """
|
|
59
|
+
_system_dirs=['densification','precure',r'iter-{iter:d}','capping','postcure']
|
|
60
|
+
_postsim_dirs=['anneal','equilibrate']
|
|
61
|
+
_measurement_dirs=['Tg','E']
|
|
62
|
+
_md_ensembles={'nvt':['Temperature','Potential'],'npt':['Temperature','Potential','Density'],'default':['Temperature','Potential']}
|
|
63
|
+
_indir_pfx={}
|
|
64
|
+
_indir_pfx['densification']=[r'densified-{ens:s}',r'densified-repeat-{repeat:d}-{ens:s}']
|
|
65
|
+
_indir_pfx['precure']=[r'preequilibration-{ens:s}',r'annealed',r'postequilibration-{ens:s}']
|
|
66
|
+
_indir_pfx['iter-n']=[r'1-cure_drag-stage-{stage:d}-{ens:s}',r'3-cure_relax-stage-{stage:d}-{ens:s}',r'4-cure_equilibrate-{ens:s}']
|
|
67
|
+
_indir_pfx['capping']=[r'7-cap_relax-stage-{stage:d}-{ens:s}',r'8-cap_equilibrate-{ens:s}']
|
|
68
|
+
_indir_pfx['postcure']=[r'preequilibration-{ens:s}',r'annealed',r'postequilibration-{ens:s}']
|
|
69
|
+
def _concat_from_edr(df,edr,names,add=[],add_if_missing=[('Density',0.0)]):
|
|
70
|
+
"""Concatenates rows onto dataframe df by reading data from an edr file.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
df (pandas.DataFrame): a dataframe with data read in from edr files
|
|
74
|
+
edr (str): name of a new edr file to read from
|
|
75
|
+
names (list of str): names of energy-like quantities to be read in; each must have a column in df already
|
|
76
|
+
add (list): names and values to add to dataset, defaults to []
|
|
77
|
+
add_if_missing (list): names and values to add if not found in edr file, defaults to [('Density',0.0)]
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
tuple: the dataframe and the scalar value of last time before this data increment is read in
|
|
81
|
+
"""
|
|
82
|
+
xshift=0.0
|
|
83
|
+
if not df.empty: xshift=df.iloc[-1]['time(ps)']
|
|
84
|
+
if len(names)==0: return df,xshift
|
|
85
|
+
# print(f'{add_if_missing}')
|
|
86
|
+
this_df=gmx_energy_trace(edr,names,xshift=xshift)
|
|
87
|
+
for m in add:
|
|
88
|
+
nm,val=m
|
|
89
|
+
this_df[nm]=np.ones(this_df.shape[0],dtype=type(val))*val
|
|
90
|
+
for m in add_if_missing:
|
|
91
|
+
nm,val=m
|
|
92
|
+
if not nm in this_df:
|
|
93
|
+
this_df[nm]=np.ones(this_df.shape[0],dtype=type(val))*val
|
|
94
|
+
df=pd.concat((df,this_df),ignore_index=True)
|
|
95
|
+
return df,xshift
|
|
96
|
+
|
|
97
|
+
def postsim_density_evolution(proj_dir,append_dirname=False):
|
|
98
|
+
"""Returns a single dataframe that is a concatenation of the csv files in the 'postsim' subdirectories.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
proj_dir (str): name of complete project directory
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
pandas.DataFrame: the dataframe
|
|
105
|
+
"""
|
|
106
|
+
if not os.path.exists(proj_dir): return
|
|
107
|
+
sysd=os.path.join(proj_dir,'postsim')
|
|
108
|
+
fn=[f'{sysd}/anneal/anneal.csv',f'{sysd}/equilibrate/equilibrate.csv']
|
|
109
|
+
df=pd.DataFrame()
|
|
110
|
+
lt=0.0
|
|
111
|
+
for f in fn:
|
|
112
|
+
t=pd.read_csv(f,header=0,index_col=None)
|
|
113
|
+
logger.debug(f'shifting by {lt}')
|
|
114
|
+
t['time(ps)']+=lt
|
|
115
|
+
lt=t['time(ps)'].to_list()[-1]
|
|
116
|
+
df=pd.concat((df,t))
|
|
117
|
+
if append_dirname:
|
|
118
|
+
df.rename(columns={x:f'{x}-{proj_dir}' for x in df.columns})
|
|
119
|
+
return df
|
|
120
|
+
|
|
121
|
+
def density_evolution(proj_dir):
|
|
122
|
+
"""Returns a single dataframe containing density, temperature, number of bonds vs time by reading all edrs in the correct order from a complete project directory.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
proj_dir (str): name of complete project directory
|
|
126
|
+
|
|
127
|
+
Returns:
|
|
128
|
+
pandas.DataFrame: the dataframe
|
|
129
|
+
"""
|
|
130
|
+
if not os.path.exists(proj_dir): return
|
|
131
|
+
sysd=os.path.join(proj_dir,'systems')
|
|
132
|
+
df=pd.DataFrame()
|
|
133
|
+
xshift=0.0
|
|
134
|
+
transition_times=[xshift]
|
|
135
|
+
interval_labels=[]
|
|
136
|
+
markers=[]
|
|
137
|
+
nbonds=0
|
|
138
|
+
for subd in _system_dirs:
|
|
139
|
+
# print(f'subd {subd}')
|
|
140
|
+
if subd==r'iter-{iter:d}' or subd=='capping' or subd=='densification':
|
|
141
|
+
# loooking for iterations
|
|
142
|
+
# print(iter_mark0)
|
|
143
|
+
iter=1
|
|
144
|
+
iter_subd_rel=subd if subd in ['capping','densification'] else subd.format(iter=iter)
|
|
145
|
+
iter_subd=os.path.join(sysd,iter_subd_rel)
|
|
146
|
+
while os.path.exists(iter_subd):
|
|
147
|
+
logger.info(f'Reading edrs in {iter_subd_rel}...')
|
|
148
|
+
if r'iter' in subd:
|
|
149
|
+
markers.append(df.iloc[-1]['time(ps)'])
|
|
150
|
+
dirkey='iter-n' if subd==r'iter-{iter:d}' else subd
|
|
151
|
+
if subd==r'iter-{iter:d}':
|
|
152
|
+
if os.path.exists(os.path.join(iter_subd,'2-cure_update-bonds.csv')):
|
|
153
|
+
bdf=pd.read_csv(os.path.join(iter_subd,'2-cure_update-bonds.csv'),sep=r'\s+',header=0,index_col=None)
|
|
154
|
+
nbonds+=bdf.shape[0]
|
|
155
|
+
else:
|
|
156
|
+
if os.path.exists(os.path.join(iter_subd,'6-cap_update-bonds.csv')):
|
|
157
|
+
bdf=pd.read_csv(os.path.join(iter_subd,'6-cap_update-bonds.csv'),sep=r'\s+',header=0,index_col=None)
|
|
158
|
+
nbonds+=bdf.shape[0]
|
|
159
|
+
# print(f'iter_subd {iter_subd} dirkey {dirkey}')
|
|
160
|
+
for pfx in _indir_pfx[dirkey]:
|
|
161
|
+
# print(f'pfx {pfx}')
|
|
162
|
+
if r'stage' in pfx or r'repeat' in pfx:
|
|
163
|
+
stg=1
|
|
164
|
+
stg_present=any([os.path.exists(os.path.join(iter_subd,pfx.format(stage=stg,repeat=stg,ens=x))+r'.edr') for x in _md_ensembles])
|
|
165
|
+
# print(f'{iter_subd} {stg} {pfx} stg_present {stg_present}')
|
|
166
|
+
while stg_present:
|
|
167
|
+
for ens,qtys in _md_ensembles.items():
|
|
168
|
+
edr_pfx=os.path.join(iter_subd,pfx.format(stage=stg,repeat=stg,ens=ens))
|
|
169
|
+
gro=edr_pfx+r'.gro'
|
|
170
|
+
# print(edr_pfx,os.path.exists(edr_pfx+r'.edr'))
|
|
171
|
+
if os.path.exists(edr_pfx+r'.edr'):
|
|
172
|
+
density=0.0
|
|
173
|
+
if ens!='npt':
|
|
174
|
+
if os.path.exists(gro):
|
|
175
|
+
density=density_from_gro(gro,os.path.join(proj_dir,'molecules/parameterized'))
|
|
176
|
+
# print(f'density from gro {density}')
|
|
177
|
+
df,xshift=_concat_from_edr(df,edr_pfx,qtys,add=[('nbonds',nbonds)],add_if_missing=[('Density',density)])
|
|
178
|
+
transition_times.append(xshift)
|
|
179
|
+
interval_labels.append([edr_pfx])
|
|
180
|
+
stg+=1
|
|
181
|
+
stg_present=any([os.path.exists(os.path.join(iter_subd,pfx.format(stage=stg,repeat=stg,ens=x))+r'.edr') for x in _md_ensembles])
|
|
182
|
+
# print(f'->{iter_subd} {stg} {pfx} stg_present {stg_present}')
|
|
183
|
+
elif r'equil' in pfx or r'densif' in pfx:
|
|
184
|
+
eq_present=any([os.path.exists(os.path.join(iter_subd,pfx.format(ens=x))+r'.edr') for x in _md_ensembles])
|
|
185
|
+
# print(f'{iter_subd} eq_present? {eq_present}')
|
|
186
|
+
if eq_present:
|
|
187
|
+
for ens,qtys in _md_ensembles.items():
|
|
188
|
+
edr_pfx=os.path.join(iter_subd,pfx.format(ens=ens))
|
|
189
|
+
gro=edr_pfx+r'.gro'
|
|
190
|
+
# print(edr_pfx,os.path.exists(edr_pfx+r'.edr'),ens,qtys)
|
|
191
|
+
if os.path.exists(edr_pfx+r'.edr'):
|
|
192
|
+
density=0.0
|
|
193
|
+
if ens!='npt':
|
|
194
|
+
if os.path.exists(gro):
|
|
195
|
+
density=density_from_gro(gro,os.path.join(proj_dir,'molecules/parameterized'))
|
|
196
|
+
df,xshift=_concat_from_edr(df,edr_pfx,qtys,add=[('nbonds',nbonds)],add_if_missing=[('Density',density)])
|
|
197
|
+
transition_times.append(xshift)
|
|
198
|
+
interval_labels.append([edr_pfx])
|
|
199
|
+
iter+=1
|
|
200
|
+
iter_subd_rel=subd.format(iter=iter)
|
|
201
|
+
iter_subd=os.path.join(sysd,iter_subd_rel) if r'iter' in subd else 'I_BET_THIS_FILE_DNE'
|
|
202
|
+
# if r'iter' in subd: markers.append(df.iloc[-1]['time (ps)'])
|
|
203
|
+
else:
|
|
204
|
+
this_subd=os.path.join(sysd,subd)
|
|
205
|
+
logger.info(f'Reading edrs in {subd}...')
|
|
206
|
+
|
|
207
|
+
for pfx in _indir_pfx[subd]:
|
|
208
|
+
# print(f'this_subd {this_subd} pfx {pfx}')
|
|
209
|
+
if 'ens' in pfx:
|
|
210
|
+
for ens,qtys in _md_ensembles.items():
|
|
211
|
+
edr_pfx=os.path.join(this_subd,pfx.format(ens=ens))
|
|
212
|
+
gro=edr_pfx+r'.gro'
|
|
213
|
+
# print(edr_pfx,os.path.exists(edr_pfx+r'.edr'))
|
|
214
|
+
# print(gro,os.path.exists(gro))
|
|
215
|
+
if os.path.exists(edr_pfx+r'.edr'):
|
|
216
|
+
density=0.0
|
|
217
|
+
if ens=='nvt' and os.path.exists(gro):
|
|
218
|
+
density=density_from_gro(gro,os.path.join(proj_dir,'molecules/parameterized'))
|
|
219
|
+
df,xshift=_concat_from_edr(df,edr_pfx,qtys,add=[('nbonds',nbonds)],add_if_missing=[('Density',density)])
|
|
220
|
+
transition_times.append(xshift)
|
|
221
|
+
interval_labels.append([edr_pfx])
|
|
222
|
+
else:
|
|
223
|
+
edr_pfx=os.path.join(this_subd,pfx.format(ens=ens))
|
|
224
|
+
gro=edr_pfx+r'.gro'
|
|
225
|
+
# print(edr_pfx,os.path.exists(edr_pfx+r'.edr'))
|
|
226
|
+
if os.path.exists(edr_pfx+r'.edr'):
|
|
227
|
+
density=0.0
|
|
228
|
+
if os.path.exists(gro):
|
|
229
|
+
density=density_from_gro(gro,os.path.join(proj_dir,'molecules/parameterized'))
|
|
230
|
+
df,xshift=_concat_from_edr(df,edr_pfx,_md_ensembles['default'],add=[('nbonds',nbonds)],add_if_missing=[('Density',density)])
|
|
231
|
+
transition_times.append(xshift)
|
|
232
|
+
interval_labels.append([edr_pfx])
|
|
233
|
+
return df,transition_times,markers,interval_labels
|
|
234
|
+
|
|
235
|
+
def graph_from_bondsfile(bondsfile):
|
|
236
|
+
"""Generates a networkx Graph in which each node is a molecule (from 'mi' and 'mj' records in the bondsfile) and edges indicate two molecules are joined by a covalent bond.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
bondsfile (str): name of bondsfile
|
|
240
|
+
|
|
241
|
+
Returns:
|
|
242
|
+
networkx.Graph: the graph
|
|
243
|
+
"""
|
|
244
|
+
G=nx.Graph()
|
|
245
|
+
df=pd.read_csv(bondsfile,header=0,index_col=None,sep=r'\s+')
|
|
246
|
+
for i,r in df.iterrows():
|
|
247
|
+
G.add_edge(r['mi'],r['mj'])
|
|
248
|
+
return G
|
|
249
|
+
|
|
250
|
+
def mwbxl(G:nx.Graph,crosslinker='GMA',monomer='STY'):
|
|
251
|
+
"""Computes the histogram of monomer counts 'n' between crosslinking sites using a molecular connectivity graph; used mainly for vinyl-based polymerizations.
|
|
252
|
+
|
|
253
|
+
Args:
|
|
254
|
+
G (nx.Graph): molecular connectivity graph
|
|
255
|
+
crosslinker (str): name of crosslinker molecule, defaults to 'GMA'
|
|
256
|
+
monomer (str): name of monomer, defaults to 'STY'
|
|
257
|
+
|
|
258
|
+
Returns:
|
|
259
|
+
pd.DataFrame: a dataframe of 'n' and 'count'
|
|
260
|
+
"""
|
|
261
|
+
xG=G.copy()
|
|
262
|
+
# traverse edges and label hetero/homo participants
|
|
263
|
+
for n in xG.nodes():
|
|
264
|
+
xG.nodes[n]['neighbor_count']=0
|
|
265
|
+
for u,v in xG.edges():
|
|
266
|
+
n,m=xG.nodes[u],xG.nodes[v]
|
|
267
|
+
n['neighbor_count']+=1
|
|
268
|
+
m['neighbor_count']+=1
|
|
269
|
+
if n['molecule_name']!=m['molecule_name']:
|
|
270
|
+
n['monomer_type']='hetero'
|
|
271
|
+
m['monomer_type']='hetero'
|
|
272
|
+
else:
|
|
273
|
+
n['monomer_type']='homo'
|
|
274
|
+
m['monomer_type']='homo'
|
|
275
|
+
wipe_us=[]
|
|
276
|
+
for n in xG.nodes():
|
|
277
|
+
if xG.nodes[n]['molecule_name']==crosslinker:
|
|
278
|
+
wipe_us.append(n)
|
|
279
|
+
xG.remove_nodes_from(wipe_us)
|
|
280
|
+
chaintype=[]
|
|
281
|
+
ch=nx.connected_components(xG)
|
|
282
|
+
mwhist={}
|
|
283
|
+
nb=0
|
|
284
|
+
for c in ch:
|
|
285
|
+
nhetero=0
|
|
286
|
+
for n in c:
|
|
287
|
+
node=xG.nodes[n]
|
|
288
|
+
if node.get('monomer_type','none')=='hetero':
|
|
289
|
+
nhetero+=1
|
|
290
|
+
if nhetero==0:
|
|
291
|
+
chaintype.append('isolated')
|
|
292
|
+
elif nhetero==1:
|
|
293
|
+
if len(c)==1:
|
|
294
|
+
node=list(xG.nodes.values())[0]
|
|
295
|
+
if node['neighbor_count']==1:
|
|
296
|
+
chaintype.append('dangling')
|
|
297
|
+
elif node['neighbor_count']==2:
|
|
298
|
+
chaintype.append('bridging')
|
|
299
|
+
if len(c) not in mwhist:
|
|
300
|
+
mwhist[len(c)]=0
|
|
301
|
+
mwhist[len(c)]+=1
|
|
302
|
+
else:
|
|
303
|
+
chaintype.append('dangling')
|
|
304
|
+
elif nhetero==2:
|
|
305
|
+
chaintype.append('bridging')
|
|
306
|
+
if len(c) not in mwhist:
|
|
307
|
+
mwhist[len(c)]=0
|
|
308
|
+
mwhist[len(c)]+=1
|
|
309
|
+
nb+=1
|
|
310
|
+
maxlen=max(list(mwhist.keys()))
|
|
311
|
+
mw=[]
|
|
312
|
+
counts=[]
|
|
313
|
+
for n in range(maxlen):
|
|
314
|
+
cnt=mwhist.get(n,0)
|
|
315
|
+
mw.append(n)
|
|
316
|
+
counts.append(cnt)
|
|
317
|
+
df=pd.DataFrame({'n':mw,'counts':counts})
|
|
318
|
+
return df
|
|
319
|
+
|
|
320
|
+
def clusters(G:nx.Graph):
|
|
321
|
+
"""Performs a clustering analysis and returns a histogram of cluster sizes (in numbers of molecules) as a pandas DataFrame.
|
|
322
|
+
|
|
323
|
+
Args:
|
|
324
|
+
G (nx.Graph): molecular connectivity graph
|
|
325
|
+
|
|
326
|
+
Returns:
|
|
327
|
+
pd.DataFrame: cluster size histogram
|
|
328
|
+
"""
|
|
329
|
+
counts={}
|
|
330
|
+
for c in sorted(nx.connected_components(G),key=len,reverse=True):
|
|
331
|
+
size=len(c)
|
|
332
|
+
if not size in counts:
|
|
333
|
+
counts[size]=0
|
|
334
|
+
counts[size]+=1
|
|
335
|
+
sizes=[]
|
|
336
|
+
numbers=[]
|
|
337
|
+
for s,c in counts.items():
|
|
338
|
+
sizes.append(s)
|
|
339
|
+
numbers.append(c)
|
|
340
|
+
df=pd.DataFrame({'sizes':sizes,'counts':numbers})
|
|
341
|
+
df.sort_values(by='sizes',ascending=False,inplace=True)
|
|
342
|
+
return df
|
|
343
|
+
|
|
344
|
+
def compute_tg(T,v,n_points=[10,20]):
|
|
345
|
+
"""Performs a Tg determination from (volume or density) vs temperature data by fitting lines to the low-T region and another to the high-T region, taking Tg as the temperature at which they intersect.
|
|
346
|
+
|
|
347
|
+
Args:
|
|
348
|
+
T (numpy.array): temperature values
|
|
349
|
+
v (numpy.array): volume or density data
|
|
350
|
+
n_points (list): number of points on the low and high side to fit lines to, defaults to [10,20]
|
|
351
|
+
"""
|
|
352
|
+
def func(x,a,b):
|
|
353
|
+
return x*a+b
|
|
354
|
+
hot_par=[]
|
|
355
|
+
cold_par=[]
|
|
356
|
+
x=np.array(T)[:n_points[0]]
|
|
357
|
+
y=np.array(v)[:n_points[0]]
|
|
358
|
+
# logger.info(f'Tg: {len(x)} data points on cold side; begins at {x[0]}')
|
|
359
|
+
cold_par,pcov=curve_fit(func,x,y)
|
|
360
|
+
sse=(y-func(x,*cold_par))**2
|
|
361
|
+
sst=(y-x.mean())**2
|
|
362
|
+
r2=1-sse.sum()/sst.sum()
|
|
363
|
+
# logger.info(f'cold fit: {cold_par} {r2}')
|
|
364
|
+
|
|
365
|
+
x=np.array(T)[-n_points[1]:]
|
|
366
|
+
y=np.array(v)[-n_points[1]:]
|
|
367
|
+
# logger.info(f'Tg: {len(x)} data points on hot side; ends at {x[-1]}')
|
|
368
|
+
hot_par,pcov=curve_fit(func,x,y)
|
|
369
|
+
sse=(y-func(x,*hot_par))**2
|
|
370
|
+
sst=(y-x.mean())**2
|
|
371
|
+
r2=1-sse.sum()/sst.sum()
|
|
372
|
+
# logger.info(f'hot fit: {hot_par} {r2}')
|
|
373
|
+
Tg=-1
|
|
374
|
+
if len(hot_par)>0 and len(cold_par)>0:
|
|
375
|
+
Tg=-(hot_par[1]-cold_par[1])/(hot_par[0]-cold_par[0])
|
|
376
|
+
return Tg,cold_par,hot_par
|
|
377
|
+
|
|
378
|
+
def compute_E(strain,stress,fit_domain=[10,100]):
|
|
379
|
+
"""Computes the Young's modulus by performing a linear fit to an elastic regime in stress-vs-strain data.
|
|
380
|
+
|
|
381
|
+
Args:
|
|
382
|
+
strain (numpy.array): strain values
|
|
383
|
+
stress (numpy.array): stress values
|
|
384
|
+
fit_domain (list): domain over which fit is made, defaults to [10,100]
|
|
385
|
+
|
|
386
|
+
Returns:
|
|
387
|
+
tuple(float,float): E and R2 from fit
|
|
388
|
+
"""
|
|
389
|
+
x=np.array(strain[fit_domain[0]:fit_domain[1]])
|
|
390
|
+
y=np.array(stress[fit_domain[0]:fit_domain[1]])
|
|
391
|
+
# logger.info(f'x: {x[0]} -> {x[-1]}')
|
|
392
|
+
# logger.info(f'y: {y[0]} -> {y[-1]}')
|
|
393
|
+
def func(x,a):
|
|
394
|
+
return a*x
|
|
395
|
+
popt,pcov=curve_fit(func,x,y,p0=(1000.0))
|
|
396
|
+
sse=(y-func(x,*popt))**2
|
|
397
|
+
sst=(y-y.mean())**2
|
|
398
|
+
r2=1-sse.sum()/sst.sum()
|
|
399
|
+
return popt[0],r2
|