Package reflectometry :: Package model1d :: Package model :: Module profile

Source Code for Module reflectometry.model1d.model.profile

  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  #============================================================= 
15 -class Profile:
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 # We don't count the incident and substrate layers 86 self.numlayers = len(depth)-2 87 # Add space for incident and final depths. 88 self.names = names 89 90 # Later we can deal with rough which is varying 91 # depth and rough are shared parameters 92 layers_num = len(self.names) 93 94 # Init the roughness 95 self.rough = vector(layers_num-1) 96 self.initRough(layers_num-1, rough) 97 98 # Init the depth 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 # build layers 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 # For store current profile 135 self.z=None 136 self.p=None
137 138
139 - def refresh( self ):
140 self.calc_offsets() 141 self.numlayers = len(self.depth)-2
142 143
144 - def _defaultNoLayer(self):
145 return [NoLayer() for L in self.Lrho]
146 147
148 - def _toLayer(self, vArray):
149 # Turn this description into a layer 150 return [Layer(v) for v in vArray]
151 152
153 - def calc_offsets(self):
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
178 - def _getRange(self, p, oldlim ):
179 try: _min = min( p._val ) 180 except: _min = 0.0 181 182 try: _max = max( p._val ) 183 except: _max = 0.0 184 185 if _min > oldlim[0]: _min = oldlim[0] 186 if _max < oldlim[1]: _max = oldlim[1] 187 188 return [ _min, _max ]
189 190
191 - def _getTRange(self, p, oldlim ):
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
204 - def calcMarker(self):
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
232 - def getCurrProfiles(self):
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 #z = numpy.linspace(self.offset[0],self.offset[-1],n) 244 step = 0.2 245 z = numpy.arange( self.offset[0], self.offset[-1]+step-1e-8, step) 246 247 # TODO: only build the profiles we need 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 # Store it for later use(eg. theoretical calculation) 264 #_step = (self.offset[-1]- self.offset[0])/float(n) 265 #_z= numpy.ones( n )*_step 266 #_z[0] = 0; _z[n-1] = 0 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
277 - def __getitem__(self, n):
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
288 - def __delitem__(self, n):
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
307 - def find(self, z):
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
322 - def thickness(self):
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
330 - def insertlayer(self, n, depth, rho, rough=None):
331 """ 332 Insert a new layer after layer n. 333 334 Not implemented. 335 """ 336 self.calc_offsets()
337 338
339 - def initDepth(self, n, depth):
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 #self.depth = numpy.concatenate( ( [ 3*self.rough[0] ], 351 # depthVal[1:-1], 352 # [ 3*self.rough[0] ] ) ) 353 self.depth = depthVal
354 355 356
357 - def initRough(self, n, rough):
358 """ Initialize the rough """ 359 self.rough=[] 360 361 for i in xrange( n ): 362 if isvector(rough[i]): 363 if len(rough[i])==3: self.rough.append( rough[i][ 1] ) 364 else: self.rough.append( tFloat(rough[i]) ) 365 366 else: # Scalar 367 self.rough.append( tFloat(rough[i]) )
368 369
370 - def initRho(self, n, rho):
371 """ Initialize the rho """ 372 self.rho=[] 373 for i in xrange( n ): 374 if isfactory(rho[i]): self.rho.append( rho[i] ) 375 else: self.rho.append( tFloat( rho[i]) )
376 377
378 - def initMu(self, n, mu):
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
386 - def initPhi(self, n, phi):
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
394 - def initTheta(self, n, t):
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