1 """
2 Reflectometry profile Class.
3 """
4
5 import numpy
6
7 from layer import Layer
8 from auxs import tFloat, vector, isvector, isfactory
9 from bspline import BSpline
10 from tetheredPolymer import tetheredPolymer, TetheredPolymer, brush, Brush
11 from calcProfile import build_profile
12
13
14
16 """
17 Reflectometry depth profile class.
18
19 TODO: add support for volume fraction
20 TODO: add support for multilayer repeat structures
21 TODO: separate roughness for magnetism layers
22 TODO: more flexible interface functions
23 """
24 - def __init__(self,
25 names = None,
26 depth = None,
27 rough = None,
28 rho = None,
29 mu = None,
30 phi = None,
31 theta = None,
32 max_rough=3
33 ):
34 """
35 Create a new reflectometry 1D profile model. A profile consists of
36 four cross sections --- scattering length density rho, absorption
37 profile mu, magnetic scattering P and magnetic angle theta.
38
39 The cross-sections are made up of list layers, all the same length,
40 or None if that cross section isn't defined. The top layer and the
41 bottom layer are semi-infinite slabs. The middle layers each have
42 a depth associated with them. Between each pair of layers is an
43 interface, with associated roughness to indicate how much diffusion
44 there is between the layers. This means a system which is made up
45 of n layers (including incident medium and substrate) has n-1
46 roughness values and n-2 depth values.
47
48 The blend function breaks down if the roughness is too large. Use
49 max_rough=k to set k*rough <= depth. A value of 3 yields a smooth
50 profile.
51
52 Each layer can have a name. The name can use TeX style math formatting
53 such as r'$\rm{Si}\rm{O}_2' for silicon dioxide.
54
55 Various layer types are available. As of this writing
56 they include:
57 NoLayer - the profile is not defined for this region
58 FlatLayer - a flat section
59 SlopeLayer - a linear section
60 SplineLayer - a cubic B-spline section
61 JoinLayer - extend the previous layer with the next depth
62
63 Use JoinLayer to create e.g., magnetic profiles which extend
64 across multple layers and can be fit with splines.
65
66 The resulting model has a number of attributes:
67 magnetic is True if there are magnetic layers
68 absorbing is True if there are absorbing layers
69 d is the depth of each layer, including the nominal
70 depth of the substrate and incident medium required
71 to support roughness (out to 3 sigma)
72 offset is the location of each layer interface, including
73 values for leading and trailing edge of the incident
74 medium and substrate roughness
75 rho, mu, P, theta are the layers
76 rough is the roughnesses
77 names are the names of the layers
78 """
79 self.magnetic = (phi!= None)
80 self.absorbing = (mu != None)
81
82 self.empty = True
83 if len(rough) != 0:
84
85
86 self.numlayers = len(depth)-2
87
88 self.names = names
89
90
91
92 layers_num = len(self.names)
93
94
95 self.rough = vector(layers_num-1)
96 self.initRough(layers_num-1, rough)
97
98
99 self.depth = vector(layers_num)
100 self.initDepth(layers_num, depth)
101
102 self.initRho(layers_num, rho)
103 self.initMu(layers_num, mu)
104
105 if self.magnetic:
106 self.initPhi(layers_num, phi)
107 self.initTheta(layers_num, theta)
108
109 self.max_rough = max_rough
110
111
112 Lrho,Lmu,Lphi,Ltheta = None,None,None,None
113 self.Lrho = self._toLayer( self.rho )
114 self.Lmu = self._toLayer( self.mu )
115 if self.magnetic:
116 self.Lphi = self._toLayer( self.phi )
117 self.Ltheta = self._toLayer( self.theta )
118
119 if self.Lmu == None:
120 self.Lmu = self._defaultNoLayer()
121
122 if self.magnetic:
123 if self.Lphi == None: self.Lphi = self._defaultNoLayer()
124 if self.Ltheta == None: self.Ltheta = self._defaultNoLayer()
125
126 if self.rough == None:
127 self.rough = [1. for L in rho[1:]]
128
129
130 self.calc_offsets()
131
132 self.calcMarker()
133
134
135 self.z=None
136 self.p=None
137
138
142
143
145 return [NoLayer() for L in self.Lrho]
146
147
149
150 return [Layer(v) for v in vArray]
151
152
154 """
155 Recompute the offsets of each layer. This must be done after the
156 model is updated (and in particular after the depths, or top and
157 bottom roughness has changed). model.offset[0] and model.offset[-1]
158 show the maximum extent of the profile between the semi-infinite
159 uniform incident and substrate media. The other offsets show the
160 position of the interfaces.
161 """
162
163 """
164 if 3*self.rough[0] >= 100: self.depth[0] = 3*self.rough[0]
165 else: self.depth[0] = 100
166
167 if 3*self.rough[-1] >= 100: self.depth[-1] = 3*self.rough[-1]
168 else: self.depth[-1] = 100
169 """
170 self.depth[0] = 3*self.rough[0]
171 self.depth[-1] = 3*self.rough[-1]
172 if 3*self.rough[0] < 15: self.depth[0 ] = 15
173 if 3*self.rough[-1] < 15: self.depth[-1] = 15
174
175 self.offset=numpy.concatenate(((0,),numpy.cumsum(self.depth)))-self.depth[0]
176
177
189
190
192 try: _min = min( p._val[0], p._val[1] )
193 except: _min = 0.0
194
195 try: _max = max( p._val[0], p._val[1] )
196 except: _max = 0.0
197
198 if _min > oldlim[0]: _min = oldlim[0]
199 if _max < oldlim[1]: _max = oldlim[1]
200
201 return [ _min, _max ]
202
203
205 """
206 Find the range of the marker( slope, spline ) in y axis
207 """
208 _ylim = [0.0, 0.0]
209 for i in xrange( len(self.Lrho) ):
210 if isfactory( self.rho[i] ):
211 if self.rho[i].build()[:8]=="Tethered":
212 _ylim = self._getTRange( self.Lrho[i], _ylim )
213 else:
214 _ylim = self._getRange( self.Lrho[i], _ylim )
215
216 if isfactory( self.mu[i] ):
217 if self.mu[i].build()[:8]=="Tethered":
218 _ylim = self._getTRange( self.Lmu[i], _ylim )
219 else:
220 _ylim = self._getRange( self.Lmu[i], _ylim )
221
222 if self.magnetic:
223 if isfactory( self.phi[i] ):
224 if self.phi[i].build()[:8]=="Tethered":
225 _ylim = self._getTRange( self.Lphi[i], _ylim )
226 else:
227 _ylim = self._getRange( self.Lphi[i], _ylim )
228
229 return _ylim
230
231
233 return (self.z, self.p)
234
235
236 - def calc(self, n=200):
237 """
238 Compute the profiles. This uses equidistance steps in z.
239
240 TODO: use dz as the criterion rather than n
241 TODO: Collapse excess layers to make computing reflectivity cheaper
242 """
243
244 step = 0.2
245 z = numpy.arange( self.offset[0], self.offset[-1]+step-1e-8, step)
246
247
248 if self.magnetic:
249 p = [build_profile(self.depth,
250 self.rough,
251 c,
252 z,
253 max_rough=self.max_rough)
254 for c in (self.Lrho,self.Lmu,self.Lphi,self.Ltheta)]
255 else:
256 p = [build_profile(self.depth,
257 self.rough,
258 c,
259 z,
260 max_rough=self.max_rough)
261 for c in (self.Lrho,self.Lmu) ]
262
263
264
265
266
267
268 dz = step*numpy.ones(z.shape)
269 dz[0] = dz[-1] = 0
270
271 self.z = dz
272 self.p = p
273
274 return z,p
275
276
278 """
279 model[i] returns the tuple (rho,mu,P,theta) with the contents for
280 each cross-section in that layer.
281 """
282 if self.magnetic:
283 return self.Lrho[n],self.Lmu[n],self.Lphi[n],self.Ltheta[n]
284 else:
285 return self.Lrho[n],self.Lmu[n]
286
287
289 """
290 del model[i] removes a layer.
291 """
292 if n == 0 or n == len(depth)-1:
293 raise IndexError("Cannot delete incident medium or substrate")
294 if self.magnetic:
295 for L in [self.Lrho, self.Lmu, self.Lphi, self.Ltheta]:
296 del L[n]
297 else:
298 for L in [self.Lrho, self.Lmu]:
299 del L[n]
300
301 del self.depth[n]
302 del self.name[n]
303 del self.rough[n]
304 self.calc_offset()
305
306
308 """
309 Find the layer containing the z value.
310 """
311 idx = self.offset[1:-1].searchsorted(z)
312 return idx
313
314
315 - def span(self,i,n):
316 """
317 Compute the total depth spanned by n layers starting at layer i.
318 """
319 return numpy.sum(depth[i:i+n])
320
321
323 """
324 Compute the total thickness of the system, excluding roughness
325 extending into the incident medium or substrate.
326 """
327 return self.offset[-2]
328
329
331 """
332 Insert a new layer after layer n.
333
334 Not implemented.
335 """
336 self.calc_offsets()
337
338
340 """ Initialize the depth """
341 depthVal=[]
342 for i in xrange( n ):
343 if isvector(depth[i]):
344 if len(depth[i])==3: depthVal.append( depth[i][1] )
345 else: depthVal.append( tFloat(depth[i]) )
346
347 else:
348 depthVal.append( tFloat(depth[i]) )
349
350
351
352
353 self.depth = depthVal
354
355
356
368
369
376
377
379 """ Initialize the mu """
380 self.mu = []
381 for i in xrange( n ):
382 if isfactory(mu[i]): self.mu.append( mu[i] )
383 else: self.mu.append( tFloat(mu[i]) )
384
385
387 """ Initialize the phi """
388 self.phi = []
389 for i in xrange( n ):
390 if isfactory(phi[i]): self.phi.append( phi[i] )
391 else: self.phi.append( tFloat( phi[i]) )
392
393
395 """ Initialize the theta """
396 self.theta = []
397 for i in xrange( n ):
398 if isfactory( t[i] ): self.theta.append( t[i] )
399 else: self.theta.append( tFloat( t[i]) )
400