Skip to content

Commit 76d188d

Browse files
committed
aded codes for the cgmd bilayer walkthrough and autoplot demo
1 parent 2709ba8 commit 76d188d

File tree

7 files changed

+1375
-0
lines changed

7 files changed

+1375
-0
lines changed

codes/mesh.py

Lines changed: 557 additions & 0 deletions
Large diffs are not rendered by default.

codes/undulate.py

Lines changed: 261 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
#!/usr/bin/env python
2+
3+
import re
4+
import numpy as np
5+
import scipy
6+
import scipy.optimize
7+
from base.tools import status
8+
9+
machine_eps = eps = np.finfo(float).eps
10+
dotplace = lambda n : re.compile(r'(\d)0+$').sub(r'\1',"%3.5f"%float(n)).rjust(8)
11+
12+
"""
13+
Plotting functions for creating undulation spectra.
14+
"""
15+
16+
def fftwrap(dat):
17+
"""
18+
Wrapper function for 2D Fourier transform.
19+
"""
20+
return np.fft.fft2(np.array(dat))
21+
22+
def perfect_collapser(xs,ys,trim=False,return_indices=False):
23+
"""
24+
Return two arrays for a "perfect" collapse.
25+
"""
26+
xsort = np.array(np.sort(list(set(xs))))
27+
if trim: xsort = xsort[1:]
28+
inds = np.argmax(np.array([xs==xsort[i] for i in range(len(xsort))]),axis=0)
29+
#---! future warning below
30+
if type(ys)==type(None): col = None
31+
else: col = np.array([np.mean(ys[np.where(inds==i)]) for i in range(len(xsort))])
32+
if return_indices: return xsort,col,inds
33+
else: return xsort,col
34+
35+
def blurry_binner_deprecated(xs,ys,bin_width=0.05,trim=True,return_mapping=False):
36+
"""
37+
Group wavevectors by bins.
38+
Retired this because it was confusing!
39+
"""
40+
blurred = (xs/bin_width).astype(int)
41+
xsort = np.array(np.sort(list(set(blurred))))
42+
if trim: xsort = xsort[1:]
43+
inds = np.argmax(np.array([blurred==xsort[i] for i in range(len(xsort))]),axis=0)
44+
if type(ys)!=np.ndarray: coly = None
45+
else: coly = np.array([np.mean(ys[np.where(inds==i)]) for i in range(len(xsort))])
46+
colx = np.array([np.mean(xs[np.where(inds==i)]) for i in range(len(xsort))])
47+
if not return_mapping: return colx,coly,inds
48+
else:
49+
#---! this takes a moment
50+
mapping = [np.where(blurred==i)[0] for i in np.sort(blurred)]
51+
return colx,coly,inds,mapping
52+
53+
def blurry_binner(xs,ys,bin_width=0.05,trim=True,return_mapping=False,mode='snapped'):
54+
"""
55+
Improved over original with sensible binning and interpolation.
56+
Removed the index return strategy in favor of handling things internally.
57+
"""
58+
#---get the range of the independent variable
59+
x0,x1 = xs.min(),xs.max()
60+
#---snap to the bin width
61+
x0b,x1b = bin_width*np.floor(x0/bin_width),bin_width*np.ceil(x1/bin_width)
62+
#---develop canonical bin markers
63+
bins = np.arange(x0b,x1b+bin_width,bin_width)
64+
#---bins are represented in the middle
65+
xmid = (bins[1:]+bins[:-1])/2.
66+
#---get the bin positions
67+
xbin = np.floor(xs/bin_width).astype(int) - np.floor(xs.min()/bin_width).astype(int)
68+
#---check that everything is in the right bin
69+
try:
70+
if not (np.all(xs<=bins[1:][xbin]) and np.all(xs>=bins[:-1][xbin])):
71+
import ipdb;ipdb.set_trace()
72+
raise Exception('binning failure!')
73+
except:
74+
import ipdb;ipdb.set_trace()
75+
#---reindex each position into bins
76+
indices = np.unique(xbin)
77+
reindex = [np.where(xbin==i)[0] for i in indices]
78+
#---snapped mode just uses the middle of each bin and takes the average of the constituents
79+
if mode=='snapped':
80+
y_out = np.array([ys[r].mean() for r in reindex])
81+
x_out = xmid[indices]
82+
#---! recommend developing an interpolation method here to shift laterally if off-center constituents
83+
else: raise
84+
#---for each point, return the indices of other points in the same bin
85+
#---...note that this is essential for developing weights in the blurry_explicit
86+
#---...where the weights end up: weights = 1./np.array([len(i) for i in q_mapping])[band]
87+
if return_mapping:
88+
mapping = [np.where(xbin==i)[0] for i in np.sort(xbin)]
89+
return x_out,y_out,mapping
90+
else: return x_out,y_out
91+
92+
def undulation_fitter(q_raw,hqs,area,initial_conditions=(20.0,0.0),residual_form='linear'):
93+
"""Like bath fitter, but for undulations."""
94+
95+
if residual_form == 'log':
96+
def residual(values):
97+
return np.sum(np.log10(values.clip(min=machine_eps))**2)/float(len(values))
98+
elif residual_form == 'linear':
99+
def residual(values):
100+
return np.sum((values-1.0)**2)/float(len(values))
101+
else: raise Exception('unclear residual form %s'%residual_form)
102+
103+
def multipliers(x,y):
104+
"""Multiplying complex matrices in the list of terms that contribute to the energy."""
105+
return x*np.conjugate(y)
106+
107+
def callback(args):
108+
"""Watch the optimization."""
109+
global Nfeval
110+
name_groups = ['kappa','gamma']
111+
text = ' step = %d '%Nfeval+' '.join([name+' = '+dotplace(val)
112+
for name,val in zip(name_groups,args)+[('error',objective(args))]])
113+
status('searching! '+text,tag='optimize')
114+
Nfeval += 1
115+
116+
def objective(args,mode='residual'):
117+
kappa,gamma = args
118+
termlist = [multipliers(x,y) for x,y in [(hqs,hqs)]]
119+
gamma = 0.0
120+
ratio = kappa/2.0*area*(termlist[0]*q_raw**4)+gamma/2.0*area*(termlist[0]*q_raw**2)
121+
if mode=='residual': return residual(ratio)
122+
elif mode=='ratio': return ratio
123+
else: raise Exception('invalid mode %s'%mode)
124+
125+
global Nfeval
126+
Nfeval = 0
127+
test_ans = objective(initial_conditions)
128+
if not isinstance(test_ans,np.floating):
129+
raise Exception('objective_residual function must return a scalar')
130+
fit = scipy.optimize.minimize(objective,
131+
#---note Newton-CG requires Jacobian, coblya is not strong enough, usw
132+
x0=tuple(initial_conditions),method='BFGS',callback=callback)
133+
return dict(fit,kappa=fit.x[0],gamma=fit.x[1])
134+
135+
def calculate_undulations(surf,vecs,fit_style=None,chop_last=False,lims=(0,1.0),
136+
perfect=False,raw=False,midplane_method=None,custom_heights=None,residual_form='log',fit_tension=False):
137+
"""
138+
Compute undulation spectrum.
139+
"""
140+
nframes,m,n = surf.shape
141+
frames = np.arange(nframes)
142+
Lx,Ly = np.mean(vecs,axis=0)[:2]
143+
lenscale = 1.
144+
qmagsshift = lenscale*np.array([[np.sqrt(
145+
((i-m*(i>m/2))/((Lx)/1.)*2*np.pi)**2+
146+
((j-n*(j>n/2))/((Ly)/1.)*2*np.pi)**2)
147+
for j in range(0,n)] for i in range(0,m)])
148+
area = (Lx*Ly/lenscale**2)
149+
150+
#---remove the supposed "average" structure
151+
if midplane_method=='flat' or midplane_method==None:
152+
surf = surf-np.mean(surf)
153+
elif midplane_method=='average':
154+
surf = surf-np.mean(surf,axis=0)
155+
elif midplane_method=='average_normal' and type(custom_heights)==type(None):
156+
raise Exception('send custom_heights for average_normal')
157+
elif midplane_method=='average_normal': surf = custom_heights
158+
else: raise Exception('invalid midplane_method %s'%midplane_method)
159+
160+
#---perform the FFT and line up the results
161+
hqs = np.array([fftwrap(surf[fr])/lenscale/np.double((m*n)) for fr in range(nframes)])
162+
y = np.reshape(np.mean(np.abs(hqs)**2,axis=0),-1)
163+
x = np.reshape(qmagsshift,-1)
164+
165+
#---default method
166+
if fit_style==None: fit_style = 'band,perfect,simple'
167+
168+
packed = {}
169+
#---choose a binning method, range method, and fitting method
170+
if fit_style in ['band,perfect,simple','band,perfect,basic',
171+
'band,perfect,fit','band,perfect,curvefit','band,blurry,curvefit','band,perfect,curvefit-crossover']:
172+
173+
if lims==None: raise Exception('fit_style %s requires lims'%fit_style)
174+
#---collapse, perfectly
175+
x2,y2 = perfect_collapser(x,x)[1],perfect_collapser(x,y)[1]
176+
goodslice = np.where(np.all((x2>lims[0],x2<lims[1]),axis=0))
177+
#---sample in the band and drop the zero mode
178+
#---! where is the zero mode dropped?
179+
x3,y3 = x2[goodslice],y2[goodslice]
180+
181+
#---apply binning method
182+
if fit_style=='band,blurry,curvefit':
183+
x3,y3 = blurry_binner(x3,y3)
184+
185+
186+
#---perform the fit
187+
if fit_style=='band,perfect,simple':
188+
#---the simple method is a crude way to do the fit, by fixing the exponent and then using the fact
189+
#---...that the y-axis centroid of the positions must determine the
190+
#---we recommend against using this method for further calculaiton. useful as a comparison only.
191+
if fit_tension: raise Exception('cannot do simple with fit_tension')
192+
kappa,gamma = 2.0*np.mean(1/((y3*x3**4)*Lx*Ly/lenscale**2)),0.0
193+
elif fit_style=='band,perfect,basic':
194+
if fit_tension: raise Exception('cannot do basic with fit_tension')
195+
#---in this method we use polyfit to do a linear fit in the log space
196+
#---note that exponent is free for this method and we have no specific residual form
197+
c0,c1 = np.polyfit(np.log10(x3),np.log10(y3),1)
198+
#---fit would be: 10**c1*x3**(c0)
199+
#---! just spitballing here
200+
kappa,gamma = 1./(10**c1*area)*2.0,0.0
201+
#---save for debugging
202+
packed['linear_fit_in_log'] = dict(c0=c0,c1=c1)
203+
elif fit_style in ['band,perfect,curvefit','band,blurry,curvefit']:
204+
#---in this method we use the scipy curve_fit function
205+
exponent = 4.0
206+
kwargs = {}
207+
kwargs.update(bounds=((5.0,-1.0),(10**2.0,1.0)))
208+
kwargs.update(maxfev=10**5)
209+
if residual_form=='linear':
210+
def hqhq(q_raw,kappa,sigma):
211+
if not fit_tension: sigma = 0.0
212+
return 1.0/(area/2.0*(kappa*q_raw**(exponent)+sigma*q_raw**2))
213+
fit = scipy.optimize.curve_fit(hqhq,x3,y3,**kwargs)
214+
kappa,gamma = fit[0][0],fit[0][1]
215+
print(gamma)
216+
elif residual_form=='log':
217+
#---! deprecated but works
218+
if False:
219+
def hqhq(q_raw,kappa,sigma):
220+
sigma = 0.0
221+
return np.log10(2.0/area/kappa)+-4.0*q_raw
222+
fit = scipy.optimize.curve_fit(hqhq,np.log10(x3),np.log10(y3),**kwargs)
223+
#---new method has the full expression fit in the log space
224+
else:
225+
def hqhq(q_raw,kappa,sigma):
226+
if not fit_tension: sigma = 0.0
227+
return np.log10(1.0/(area/2.0*(kappa*q_raw**(exponent)+sigma*q_raw**2)))
228+
fit = scipy.optimize.curve_fit(hqhq,x3,np.log10(y3),**kwargs)
229+
kappa,gamma = fit[0][0],fit[0][1]
230+
else: raise Exception('invalid residual_form %s'%residual_form)
231+
232+
elif fit_style=='band,perfect,curvefit-crossover':
233+
#---in this method we use the scipy curve_fit function
234+
exponent = 4.0
235+
kwargs = {}
236+
kwargs.update(bounds=((5.0,-1.0,lims[0]+0.001),(10**2.0,1.0,lims[1]-0.001)))
237+
kwargs.update(maxfev=10**5)
238+
if residual_form!='log': raise Exception('crossover only works with residual_form log')
239+
if fit_tension==False: raise Exception('crossover only works with fit_tension')
240+
def hqhq(q_raw,kappa,sigma,crossover):
241+
if sigma==0.0: sigma = eps
242+
regime_tension = q_raw<=crossover
243+
regime_bending = ~regime_tension
244+
heights_tension = np.log10(1.0/(area/2.0*(sigma*q_raw[regime_tension]**2.0)))
245+
heights_bending = np.log10(1.0/(area/2.0*(kappa*q_raw[regime_bending]**4.0)))
246+
return np.concatenate((heights_tension,heights_bending))
247+
fit = scipy.optimize.curve_fit(hqhq,x3,np.log10(y3),(20.0,0.001,0.02),**kwargs)
248+
kappa,gamma,crossover = fit[0][0],fit[0][1],fit[0][2]
249+
packed.update(crossover=crossover)
250+
elif fit_style=='band,perfect,fit':
251+
#---the most advanced method uses scipy.optimmize in a separate function
252+
fit = undulation_fitter(x3,y3,area,residual_form=residual_form,
253+
initial_conditions=(20.0,0.0))
254+
kappa,gamma = fit['kappa'],fit['gamma']
255+
else: raise Exception('invalid fit_style %s'%fit_style)
256+
#---return the data
257+
packed.update(kappa=kappa,points=np.transpose((x3,y3)),sigma=gamma,
258+
q_raw=x,energy_raw=y,q_binned=x2,energy_binned=y2,area=area)
259+
260+
else: raise Exception('invalid fit_style %s'%fit_style)
261+
return packed

codes/undulate_plot.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
#!/usr/bin/env python
2+
3+
import numpy as np
4+
from . undulate import calculate_undulations
5+
6+
def undulation_panel(ax,data,keys=None,art=None,title=None,lims=None,
7+
colors=None,labels=None,show_fit=True,midplane_method=None,custom_heights=None):
8+
"""
9+
Plot several undulation spectra on one panel.
10+
"""
11+
#---function for the q4-scaline
12+
func_q4 = lambda q,kappa: kappa*q**-4
13+
uspecs = {}
14+
sns = keys if keys else data.keys()
15+
for sn in sns:
16+
dat = data[sn]['data']
17+
mesh = data[sn]['data']['mesh']
18+
vecs = data[sn]['data']['vecs']
19+
surf = np.mean(data[sn]['data']['mesh'],axis=0)
20+
#---kernel of this plot: calculate the spectra here
21+
uspec = calculate_undulations(surf,vecs,chop_last=True,custom_heights=custom_heights,
22+
perfect=True,lims=lims,raw=False,midplane_method=midplane_method)
23+
uspecs[sn] = uspec
24+
x,y = uspec['q_binned'],uspec['energy_binned']
25+
label = labels[sn] if labels else sn
26+
label += '\n'+r'$\mathrm{\kappa='+('%.1f'%uspec['kappa'])+'\:k_BT}$'
27+
#---colors should be a dict over the keys
28+
color = colors[sn] if colors else None
29+
ax.plot(x,y,'o-',lw=2,markersize=5,markeredgewidth=0,c=color,label=label)
30+
ax.set_title(title)
31+
if show_fit: ax.plot(x,func_q4(x,kappa=uspec['kappa'])/np.product(vecs.mean(axis=0)[:2])/100.*2.,zorder=5,c='k',lw=1)
32+
add_undulation_labels(ax,art=art)
33+
add_std_legend(ax,loc='upper right',art=art)
34+
add_axgrid(ax,art=art)
35+
return uspecs
36+
37+
def add_undulation_labels(ax,art=None):
38+
"""
39+
Label an axes for undulation spectra.
40+
"""
41+
ax.set_ylabel(r'$\left\langle h_{q}h_{-q}\right\rangle \left(\mathrm{nm}^{2}\right)$')
42+
ax.set_xlabel(r'${\left|\mathbf{q}\right|}(\mathrm{{nm}^{-1}})$')
43+
ax.set_xscale('log')
44+
ax.set_yscale('log')
45+
46+
def add_axgrid(ax,art=None):
47+
"""
48+
Standard plot function for adding gridlines.
49+
"""
50+
ax.grid(True,linestyle='-',zorder=0,alpha=0.35)
51+
ax.set_axisbelow(True)
52+
53+
def add_std_legend(ax,loc='upper right',lw=2.0,title=None,art=None):
54+
"""
55+
Add a legend.
56+
"""
57+
h,l = ax.get_legend_handles_labels()
58+
leginds = range(len(h))
59+
legend = ax.legend([h[i] for i in leginds],[l[i] for i in leginds],
60+
loc=loc,fontsize=art['fs']['legend'],ncol=1,title=title)
61+
for legobj in legend.legendHandles: legobj.set_linewidth(lw)

0 commit comments

Comments
 (0)