source: sasmodels/sasmodels/convert.py @ 0795293

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

Add a check for the version of the incoming pagestate to skip previous versions.

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