source: sasmodels/sasmodels/convert.py @ e1ea6b5

core_shell_microgelscostrafo411magnetic_modelticket-1257-vesicle-productticket_1156ticket_1265_superballticket_822_more_unit_tests
Last change on this file since e1ea6b5 was e1ea6b5, checked in by krzywon, 7 years ago

Streamlined code and have Unified Power Rg and Lamellar PC models loading properly.

  • Property mode set to 100644
File size: 22.4 KB
Line 
1"""
2Convert models to and from sasview.
3"""
4from __future__ import print_function, division
5
6import re
7import math
8import warnings
9
10from .conversion_table import CONVERSION_TABLE
11from .core import load_model_info
12
13# List of models which SasView versions don't contain the explicit 'scale' argument.
14# When converting such a model, please update this list.
15MODELS_WITHOUT_SCALE = [
16    'teubner_strey',
17    'broad_peak',
18    'two_lorentzian',
19    "two_power_law",
20    'gauss_lorentz_gel',
21    'be_polyelectrolyte',
22    'correlation_length',
23    'fractal_core_shell',
24    'binary_hard_sphere',
25    'raspberry'
26]
27
28# List of models which SasView versions don't contain the explicit 'background' argument.
29# When converting such a model, please update this list.
30MODELS_WITHOUT_BACKGROUND = [
31    'guinier',
32]
33
34MODELS_WITHOUT_VOLFRACTION = [
35    'fractal',
36    'vesicle',
37    'multilayer_vesicle',
38]
39
40MAGNETIC_SASVIEW_MODELS = [
41    'core_shell',
42    'core_multi_shell',
43    'cylinder',
44    'parallelepiped',
45    'sphere',
46]
47
48
49# Convert new style names for polydispersity info to old style names
50PD_DOT = [
51    ("_pd", ".width"),
52    ("_pd_n", ".npts"),
53    ("_pd_nsigma", ".nsigmas"),
54    ("_pd_type", ".type"),
55    (".lower", ".lower"),
56    (".upper", ".upper"),
57    (".fittable", ".fittable"),
58    (".std", ".std"),
59    (".units", ".units"),
60    ("", "")
61    ]
62
63def _rescale(par, scale):
64    return [pk*scale for pk in par] if isinstance(par, list) else par*scale
65
66def _is_sld(model_info, id):
67    """
68    Return True if parameter is a magnetic magnitude or SLD parameter.
69    """
70    if id.startswith('M0:'):
71        return True
72    if '_pd' in id or '.' in id:
73        return False
74    for p in model_info.parameters.call_parameters:
75        if p.id == id:
76            return p.type == 'sld'
77    # check through kernel parameters in case it is a named as a vector
78    for p in model_info.parameters.kernel_parameters:
79        if p.id == id:
80            return p.type == 'sld'
81    return False
82
83def _rescale_sld(model_info, pars, scale):
84    """
85    rescale all sld parameters in the new model definition by *scale* so the
86    numbers are nicer.  Relies on the fact that all sld parameters in the
87    new model definition end with sld.  For backward conversion use
88    *scale=1e-6*.  For forward conversion use *scale=1e6*.
89    """
90    return dict((id, (_rescale(v, scale) if _is_sld(model_info, id) else v))
91                for id, v in pars.items())
92
93
94def _get_translation_table(model_info):
95    _, translation = CONVERSION_TABLE.get(model_info.id, [None, {}])
96    translation = translation.copy()
97    for p in model_info.parameters.kernel_parameters:
98        if p.length > 1:
99            newid = p.id
100            oldid = translation.get(p.id, p.id)
101            translation.pop(newid, None)
102            for k in range(1, p.length+1):
103                if newid+str(k) not in translation:
104                    translation[newid+str(k)] = oldid+str(k)
105    # Remove control parameter from the result
106    if model_info.control:
107        translation[model_info.control] = "CONTROL"
108    return translation
109
110# ========= FORWARD CONVERSION sasview 3.x => sasmodels ===========
111def _dot_pd_to_underscore_pd(par):
112    if par.endswith(".width"):
113        return par[:-6]+"_pd"
114    elif par.endswith(".type"):
115        return par[:-5]+"_pd_type"
116    elif par.endswith(".nsigmas"):
117        return par[:-8]+"_pd_nsigma"
118    elif par.endswith(".npts"):
119        return par[:-5]+"_pd_n"
120    else:
121        return par
122
123def _pd_to_underscores(pars):
124    return dict((_dot_pd_to_underscore_pd(k), v) for k, v in pars.items())
125
126def _convert_pars(pars, mapping):
127    """
128    Rename the parameters and any associated polydispersity attributes.
129    """
130    newpars = pars.copy()
131    for new, old in mapping.items():
132        if old == new: continue
133        if old is None: continue
134        for underscore, dot in PD_DOT:
135            source = old+dot
136            if source in newpars:
137                if new is not None:
138                    target = new+dot
139                else:
140                    target = None
141                if source != target:
142                    if target:
143                        newpars[target] = pars[old+dot]
144                    del newpars[source]
145    return newpars
146
147def _conversion_target(model_name):
148    """
149    Find the sasmodel name which translates into the sasview name.
150
151    Note: *CoreShellEllipsoidModel* translates into *core_shell_ellipsoid:1*.
152    This is necessary since there is only one variant in sasmodels for the
153    two variants in sasview.
154    """
155    for sasmodels_name, [sasview_name, _] in CONVERSION_TABLE.items():
156        if sasview_name == model_name:
157            return sasmodels_name
158    return None
159
160
161def _hand_convert(name, oldpars):
162    if name == 'core_shell_parallelepiped':
163        # Make sure pd on rim parameters defaults to zero
164        # ... probably not necessary.
165        oldpars['rimA.width'] = 0.0
166        oldpars['rimB.width'] = 0.0
167        oldpars['rimC.width'] = 0.0
168    elif name == 'core_shell_ellipsoid:1':
169        # Reverse translation (from new to old), from core_shell_ellipsoid.c
170        #    equat_shell = equat_core + thick_shell
171        #    polar_core = equat_core * x_core
172        #    polar_shell = equat_core * x_core + thick_shell*x_polar_shell
173        # Forward translation (from old to new), inverting reverse translation:
174        #    thick_shell = equat_shell - equat_core
175        #    x_core = polar_core / equat_core
176        #    x_polar_shell = (polar_shell - polar_core)/(equat_shell - equat_core)
177        # Auto translation (old <=> new) happens after hand_convert
178        #    equat_shell <=> thick_shell
179        #    polar_core <=> x_core
180        #    polar_shell <=> x_polar_shell
181        # So...
182        equat_core, equat_shell = oldpars['equat_core'], oldpars['equat_shell']
183        polar_core, polar_shell = oldpars['polar_core'], oldpars['polar_shell']
184        oldpars['equat_shell'] = equat_shell - equat_core
185        oldpars['polar_core'] = polar_core / equat_core
186        oldpars['polar_shell'] = (polar_shell-polar_core)/(equat_shell-equat_core)
187    elif name == 'hollow_cylinder':
188        # now uses radius and thickness
189        thickness = oldpars['radius'] - oldpars['core_radius']
190        oldpars['radius'] = thickness
191        if 'radius.width' in oldpars:
192            pd = oldpars['radius.width']*oldpars['radius']/thickness
193            oldpars['radius.width'] = pd
194    elif name == 'multilayer_vesicle':
195        if 'scale' in oldpars:
196            oldpars['volfraction'] = oldpars['scale']
197            oldpars['scale'] = 1.0
198        if 'scale.lower' in oldpars:
199            oldpars['volfraction.lower'] = oldpars['scale.lower']
200        if 'scale.upper' in oldpars:
201            oldpars['volfraction.upper'] = oldpars['scale.upper']
202        if 'scale.fittable' in oldpars:
203            oldpars['volfraction.fittable'] = oldpars['scale.fittable']
204        if 'scale.std' in oldpars:
205            oldpars['volfraction.std'] = oldpars['scale.std']
206        if 'scale.units' in oldpars:
207            oldpars['volfraction.units'] = oldpars['scale.units']
208    elif name == 'pearl_necklace':
209        pass
210        #_remove_pd(oldpars, 'num_pearls', name)
211        #_remove_pd(oldpars, 'thick_string', name)
212    elif name == 'polymer_micelle':
213        if 'ndensity' in oldpars:
214            oldpars['ndensity'] /= 1e15
215        if 'ndensity.lower' in oldpars:
216            oldpars['ndensity.lower'] /= 1e15
217        if 'ndensity.upper' in oldpars:
218            oldpars['ndensity.upper'] /= 1e15
219    elif name == 'rpa':
220        # convert scattering lengths from femtometers to centimeters
221        for p in "L1", "L2", "L3", "L4":
222            if p in oldpars:
223                oldpars[p] /= 1e-13
224            if p + ".lower" in oldpars:
225                oldpars[p + ".lower"] /= 1e-13
226            if p + ".upper" in oldpars:
227                oldpars[p + ".upper"] /= 1e-13
228    elif name == 'spherical_sld':
229        oldpars["CONTROL"] = 0
230        i = 0
231        while "nu_inter" + str(i) in oldpars:
232            oldpars["CONTROL"] += 1
233            i += 1
234    elif name == 'teubner_strey':
235        # basically undoing the entire Teubner-Strey calculations here.
236        #    drho = (sld_a - sld_b)
237        #    k = 2.0*math.pi*xi/d
238        #    a2 = (1.0 + k**2)**2
239        #    c1 = 2.0 * xi**2 * (1.0 - k**2)
240        #    c2 = xi**4
241        #    prefactor = 8.0*math.pi*phi*(1.0-phi)*drho**2*c2/xi
242        #    scale = 1e-4*prefactor
243        #    oldpars['scale'] = a2/scale
244        #    oldpars['c1'] = c1/scale
245        #    oldpars['c2'] = c2/scale
246
247        # need xi, d, sld_a, sld_b, phi=volfraction_a
248        # assume contrast is 1.0e-6, scale=1, background=0
249        sld_a, sld_b = 1.0, 0.
250        drho = sld_a - sld_b
251
252        # find xi
253        p_scale = oldpars['scale']
254        p_c1 = oldpars['c1']
255        p_c2= oldpars['c2']
256        i_1 = 0.5*p_c1/p_c2
257        i_2 = math.sqrt(math.fabs(p_scale/p_c2))
258        i_3 = 2/(i_1 + i_2)
259        xi = math.sqrt(math.fabs(i_3))
260
261        # find d from xi
262        k = math.sqrt(math.fabs(1 - 0.5*p_c1/p_c2*xi**2))
263        d = 2*math.pi*xi/k
264
265        # solve quadratic phi (1-phi) = xi/(1e-4 8 pi drho^2 c2)
266        # favour volume fraction in [0, 0.5]
267        c = xi / (1e-4 * 8.0 * math.pi * drho**2 * p_c2)
268        phi = 0.5 - math.sqrt(0.25 - c)
269
270        # scale sld_a by 1e-6 because the translator will scale it back
271        oldpars.update(volfraction_a=phi, xi=xi, d=d, sld_a=sld_a*1e-6,
272                       sld_b=sld_b, scale=1.0)
273        oldpars.pop('c1')
274        oldpars.pop('c2')
275
276    return oldpars
277
278def convert_model(name, pars, use_underscore=False):
279    """
280    Convert model from old style parameter names to new style.
281    """
282    newname = _conversion_target(name)
283    if newname is None:
284        return name, pars
285    if ':' in newname:   # core_shell_ellipsoid:1
286        model_info = load_model_info(newname[:-2])
287        # Know that the table exists and isn't multiplicity so grab it directly
288        # Can't use _get_translation_table since that will return the 'bare'
289        # version.
290        translation = CONVERSION_TABLE[newname][1]
291    else:
292        model_info = load_model_info(newname)
293        translation = _get_translation_table(model_info)
294    newpars = _hand_convert(newname, pars.copy())
295    newpars = _convert_pars(newpars, translation)
296    if not model_info.structure_factor:
297        newpars = _rescale_sld(model_info, newpars, 1e6)
298    newpars.setdefault('scale', 1.0)
299    newpars.setdefault('background', 0.0)
300    if use_underscore:
301        newpars = _pd_to_underscores(newpars)
302    return newname, newpars
303
304
305# ========= BACKWARD CONVERSION sasmodels => sasview 3.x ===========
306
307def _revert_pars(pars, mapping):
308    """
309    Rename the parameters and any associated polydispersity attributes.
310    """
311    newpars = pars.copy()
312
313    for new, old in mapping.items():
314        for underscore, dot in PD_DOT:
315            if old and old+underscore == new+dot:
316                continue
317            if new+underscore in newpars:
318                if old is not None:
319                    newpars[old+dot] = pars[new+underscore]
320                del newpars[new+underscore]
321    for k in list(newpars.keys()):
322        for underscore, dot in PD_DOT[1:]:  # skip "" => ""
323            if k.endswith(underscore):
324                newpars[k[:-len(underscore)]+dot] = newpars[k]
325                del newpars[k]
326    return newpars
327
328def revert_name(model_info):
329    oldname, _ = CONVERSION_TABLE.get(model_info.id, [None, {}])
330    return oldname
331
332def _remove_pd(pars, key, name):
333    """
334    Remove polydispersity from the parameter list.
335
336    Note: operates in place
337    """
338    # Bumps style parameter names
339    width = pars.pop(key+".width", 0.0)
340    n_points = pars.pop(key+".npts", 0)
341    if width != 0.0 and n_points != 0:
342        warnings.warn("parameter %s not polydisperse in sasview %s"%(key, name))
343    pars.pop(key+".nsigmas", None)
344    pars.pop(key+".type", None)
345    return pars
346
347def _trim_vectors(model_info, pars, oldpars):
348    _, translation = CONVERSION_TABLE.get(model_info.id, [None, {}])
349    for p in model_info.parameters.kernel_parameters:
350        if p.length_control is not None:
351            n = int(pars[p.length_control])
352            oldname = translation.get(p.id, p.id)
353            for k in range(n+1, p.length+1):
354                for _, old in PD_DOT:
355                    oldpars.pop(oldname+str(k)+old, None)
356    return oldpars
357
358def revert_pars(model_info, pars):
359    """
360    Convert model from new style parameter names to old style.
361    """
362    if model_info.composition is not None:
363        composition_type, parts = model_info.composition
364        if composition_type == 'product':
365            translation = _get_translation_table(parts[0])
366            # structure factor models include scale:scale_factor mapping
367            translation.update(_get_translation_table(parts[1]))
368        else:
369            raise NotImplementedError("cannot convert to sasview sum")
370    else:
371        translation = _get_translation_table(model_info)
372    oldpars = _revert_pars(_rescale_sld(model_info, pars, 1e-6), translation)
373    oldpars = _trim_vectors(model_info, pars, oldpars)
374
375    # Make sure the control parameter is an integer
376    if "CONTROL" in oldpars:
377        oldpars["CONTROL"] = int(oldpars["CONTROL"])
378
379    # Note: update compare.constrain_pars to match
380    name = model_info.id
381    if name in MODELS_WITHOUT_SCALE or model_info.structure_factor:
382        if oldpars.pop('scale', 1.0) != 1.0:
383            warnings.warn("parameter scale not used in sasview %s"%name)
384    if name in MODELS_WITHOUT_BACKGROUND or model_info.structure_factor:
385        if oldpars.pop('background', 0.0) != 0.0:
386            warnings.warn("parameter background not used in sasview %s"%name)
387
388    # Remove magnetic parameters from non-magnetic sasview models
389    if name not in MAGNETIC_SASVIEW_MODELS:
390        oldpars = dict((k, v) for k, v in oldpars.items() if ':' not in k)
391
392    # If it is a product model P*S, then check the individual forms for special
393    # cases.  Note: despite the structure factor alone not having scale or
394    # background, the product model does, so this is below the test for
395    # models without scale or background.
396    namelist = name.split('*') if '*' in name else [name]
397    for name in namelist:
398        if name in MODELS_WITHOUT_VOLFRACTION:
399            del oldpars['volfraction']
400        elif name == 'core_multi_shell':
401            # kill extra shells
402            for k in range(5, 11):
403                oldpars.pop('sld_shell'+str(k), 0)
404                oldpars.pop('thick_shell'+str(k), 0)
405                oldpars.pop('mtheta:sld'+str(k), 0)
406                oldpars.pop('mphi:sld'+str(k), 0)
407                oldpars.pop('M0:sld'+str(k), 0)
408                _remove_pd(oldpars, 'sld_shell'+str(k), 'sld')
409                _remove_pd(oldpars, 'thick_shell'+str(k), 'thickness')
410        elif name == 'core_shell_parallelepiped':
411            _remove_pd(oldpars, 'rimA', name)
412            _remove_pd(oldpars, 'rimB', name)
413            _remove_pd(oldpars, 'rimC', name)
414        elif name == 'hollow_cylinder':
415            # now uses radius and thickness
416            thickness = oldpars['core_radius']
417            oldpars['radius'] += thickness
418            oldpars['radius.width'] *= thickness/oldpars['radius']
419        #elif name in ['mono_gauss_coil', 'poly_gauss_coil']:
420        #    del oldpars['i_zero']
421        elif name == 'onion':
422            oldpars.pop('n_shells', None)
423        elif name == 'pearl_necklace':
424            _remove_pd(oldpars, 'num_pearls', name)
425            _remove_pd(oldpars, 'thick_string', name)
426        elif name == 'polymer_micelle':
427            if 'ndensity' in oldpars:
428                oldpars['ndensity'] *= 1e15
429        elif name == 'rpa':
430            # convert scattering lengths from femtometers to centimeters
431            for p in "L1", "L2", "L3", "L4":
432                if p in oldpars: oldpars[p] *= 1e-13
433            if pars['case_num'] < 2:
434                for k in ("a", "b"):
435                    for p in ("L", "N", "Phi", "b", "v"):
436                        oldpars.pop(p+k, None)
437                for k in "Kab,Kac,Kad,Kbc,Kbd".split(','):
438                    oldpars.pop(k, None)
439            elif pars['case_num'] < 5:
440                for k in ("a",):
441                    for p in ("L", "N", "Phi", "b", "v"):
442                        oldpars.pop(p+k, None)
443                for k in "Kab,Kac,Kad".split(','):
444                    oldpars.pop(k, None)
445        elif name == 'spherical_sld':
446            oldpars["CONTROL"] -= 1
447            # remove polydispersity from shells
448            for k in range(1, 11):
449                _remove_pd(oldpars, 'thick_flat'+str(k), 'thickness')
450                _remove_pd(oldpars, 'thick_inter'+str(k), 'interface')
451            # remove extra shells
452            for k in range(int(pars['n_shells']), 11):
453                oldpars.pop('sld_flat'+str(k), 0)
454                oldpars.pop('thick_flat'+str(k), 0)
455                oldpars.pop('thick_inter'+str(k), 0)
456                oldpars.pop('func_inter'+str(k), 0)
457                oldpars.pop('nu_inter'+str(k), 0)
458        elif name == 'stacked_disks':
459            _remove_pd(oldpars, 'n_stacking', name)
460        elif name == 'teubner_strey':
461            # basically redoing the entire Teubner-Strey calculations here.
462            volfraction = oldpars.pop('volfraction_a')
463            xi = oldpars.pop('xi')
464            d = oldpars.pop('d')
465            sld_a = oldpars.pop('sld_a')
466            sld_b = oldpars.pop('sld_b')
467            drho = 1e6*(sld_a - sld_b)  # conversion autoscaled these
468            k = 2.0*math.pi*xi/d
469            a2 = (1.0 + k**2)**2
470            c1 = 2.0 * xi**2 * (1.0 - k**2)
471            c2 = xi**4
472            prefactor = 8.0*math.pi*volfraction*(1.0-volfraction)*drho**2*c2/xi
473            scale = 1e-4*prefactor
474            oldpars['scale'] = a2/scale
475            oldpars['c1'] = c1/scale
476            oldpars['c2'] = c2/scale
477
478    #print("convert from",list(sorted(pars)))
479    #print("convert to",list(sorted(oldpars.items())))
480    return oldpars
481
482def constrain_new_to_old(model_info, pars):
483    """
484    Restrict parameter values to those that will match sasview.
485    """
486    name = model_info.id
487    # Note: update convert.revert_model to match
488    if name in MODELS_WITHOUT_SCALE or model_info.structure_factor:
489        pars['scale'] = 1
490    if name in MODELS_WITHOUT_BACKGROUND or model_info.structure_factor:
491        pars['background'] = 0
492    # sasview multiplies background by structure factor
493    if '*' in name:
494        pars['background'] = 0
495
496    # Shut off magnetism when comparing non-magnetic sasview models
497    if name not in MAGNETIC_SASVIEW_MODELS:
498        suppress_magnetism = False
499        for key in pars.keys():
500            if key.startswith("M0:"):
501                suppress_magnetism = suppress_magnetism or (pars[key] != 0)
502                pars[key] = 0
503        if suppress_magnetism:
504            warnings.warn("suppressing magnetism for comparison with sasview")
505
506    # Shut off theta polydispersity since algorithm has changed
507    if 'theta_pd_n' in pars:
508        if pars['theta_pd_n'] != 0:
509            warnings.warn("suppressing theta polydispersity for comparison with sasview")
510        pars['theta_pd_n'] = 0
511
512    # If it is a product model P*S, then check the individual forms for special
513    # cases.  Note: despite the structure factor alone not having scale or
514    # background, the product model does, so this is below the test for
515    # models without scale or background.
516    namelist = name.split('*') if '*' in name else [name]
517    for name in namelist:
518        if name in MODELS_WITHOUT_VOLFRACTION:
519            pars['volfraction'] = 1
520        if name == 'core_multi_shell':
521            pars['n'] = min(math.ceil(pars['n']), 4)
522        elif name == 'gel_fit':
523            pars['scale'] = 1
524        elif name == 'line':
525            pars['scale'] = 1
526            pars['background'] = 0
527        elif name == 'mono_gauss_coil':
528            pars['scale'] = 1
529        elif name == 'onion':
530            pars['n_shells'] = math.ceil(pars['n_shells'])
531        elif name == 'pearl_necklace':
532            pars['string_thickness_pd_n'] = 0
533            pars['number_of_pearls_pd_n'] = 0
534        elif name == 'poly_gauss_coil':
535            pars['scale'] = 1
536        elif name == 'rpa':
537            pars['case_num'] = int(pars['case_num'])
538        elif name == 'spherical_sld':
539            pars['n_shells'] = math.ceil(pars['n_shells'])
540            pars['n_steps'] = math.ceil(pars['n_steps'])
541            for k in range(1, 11):
542                pars['shape%d'%k] = math.trunc(pars['shape%d'%k]+0.5)
543            for k in range(2, 11):
544                pars['thickness%d_pd_n'%k] = 0
545                pars['interface%d_pd_n'%k] = 0
546        elif name == 'teubner_strey':
547            pars['scale'] = 1
548            if pars['volfraction_a'] > 0.5:
549                pars['volfraction_a'] = 1.0 - pars['volfraction_a']
550        elif name == 'unified_power_Rg':
551            pars['level'] = int(pars['level'])
552
553def _check_one(name, seed=None):
554    """
555    Generate a random set of parameters for *name*, and check that they can
556    be converted back to SasView 3.x and forward again to sasmodels.  Raises
557    an error if the parameters are changed.
558    """
559    from . import compare
560
561    model_info = load_model_info(name)
562
563    old_name = revert_name(model_info)
564    if old_name is None:
565        return
566
567    pars = compare.get_pars(model_info, use_demo=False)
568    pars = compare.randomize_pars(model_info, pars, seed=seed)
569    if name == "teubner_strey":
570        # T-S model is underconstrained, so fix the assumptions.
571        pars['sld_a'], pars['sld_b'] = 1.0, 0.0
572    compare.constrain_pars(model_info, pars)
573    constrain_new_to_old(model_info, pars)
574    old_pars = revert_pars(model_info, pars)
575    new_name, new_pars = convert_model(old_name, old_pars, use_underscore=True)
576    if 1:
577        print("==== %s in ====="%name)
578        print(str(compare.parlist(model_info, pars, True)))
579        print("==== %s ====="%old_name)
580        for k, v in sorted(old_pars.items()):
581            print(k, v)
582        print("==== %s out ====="%new_name)
583        print(str(compare.parlist(model_info, new_pars, True)))
584    assert name==new_name, "%r != %r"%(name, new_name)
585    for k, v in new_pars.items():
586        assert k in pars, "%s: %r appeared from conversion"%(name, k)
587        if isinstance(v, float):
588            assert abs(v-pars[k])<=abs(1e-12*v), "%s: %r  %s != %s"%(name, k, v, pars[k])
589        else:
590            assert v == pars[k], "%s: %r  %s != %s"%(name, k, v, pars[k])
591    for k, v in pars.items():
592        assert k in pars, "%s: %r not converted"%(name, k)
593
594def test_backward_forward():
595    from .core import list_models
596    for name in list_models('all'):
597        L = lambda: _check_one(name, seed=1)
598        L.description = name
599        yield L
Note: See TracBrowser for help on using the repository browser.