Index: sasmodels/core.py
===================================================================
--- sasmodels/core.py	(revision fb9a3b6f2133421705828b7ea99dcd1e96bfeba3)
+++ sasmodels/core.py	(revision 61a4bd4edf4783346bfa77b49e35468862407ac8)
@@ -126,5 +126,41 @@
 
 
-def load_model_info(model_name, force_mixture=False):
+# def load_model_info(model_name, force_mixture=False):
+#     # type: (str) -> modelinfo.ModelInfo
+#     """
+#     Load a model definition given the model name.
+
+#     *model_name* is the name of the model, or perhaps a model expression
+#     such as sphere*hardsphere or sphere+cylinder.
+
+#     *force_mixture* if true, MixtureModel will be used for combining models.
+#     Otherwise either MixtureModel will be used for addition and ProductModel
+#     will be used for multiplication
+
+#     This returns a handle to the module defining the model.  This can be
+#     used with functions in generate to build the docs or extract model info.
+#     """
+#     parts = model_name.split('+')
+#     if len(parts) > 1:
+#         # Always use MixtureModel for addition
+#         model_info_list = [load_model_info(p) for p in parts]
+#         return mixture.make_mixture_info(model_info_list)
+
+#     parts = model_name.split('*')
+#     if len(parts) > 1:
+#         if force_mixture:
+#             # Use MixtureModel for multiplication if forced
+#             model_info_list = [load_model_info(p) for p in parts]
+#             return mixture.make_mixture_info(model_info_list, operation='*')
+#         if len(parts) > 2:
+#             raise ValueError("use P*S to apply structure factor S to model P")
+#         # Use ProductModel
+#         P_info, Q_info = [load_model_info(p) for p in parts]
+#         return product.make_product_info(P_info, Q_info)
+
+#     kernel_module = generate.load_kernel_module(model_name)
+#     return modelinfo.make_model_info(kernel_module)
+
+def load_model_info(model_string):
     # type: (str) -> modelinfo.ModelInfo
     """
@@ -134,31 +170,30 @@
     such as sphere*hardsphere or sphere+cylinder.
 
-    *force_mixture* if true, MixtureModel will be used for combining models.
-    Otherwise either MixtureModel will be used for addition and ProductModel
-    will be used for multiplication
-
     This returns a handle to the module defining the model.  This can be
     used with functions in generate to build the docs or extract model info.
     """
-    parts = model_name.split('+')
-    if len(parts) > 1:
-        # Always use MixtureModel for addition
-        model_info_list = [load_model_info(p) for p in parts]
-        return mixture.make_mixture_info(model_info_list)
-
-    parts = model_name.split('*')
-    if len(parts) > 1:
-        if force_mixture:
-            # Use MixtureModel for multiplication if forced
-            model_info_list = [load_model_info(p) for p in parts]
-            return mixture.make_mixture_info(model_info_list, operation='*')
-        if len(parts) > 2:
-            raise ValueError("use P*S to apply structure factor S to model P")
-        # Use ProductModel
-        P_info, Q_info = [load_model_info(p) for p in parts]
-        return product.make_product_info(P_info, Q_info)
-
-    kernel_module = generate.load_kernel_module(model_name)
-    return modelinfo.make_model_info(kernel_module)
+    # TODO: parse an expression like form@structure to create a P(Q)*S(Q) model
+    product_parts = []
+    addition_parts = []
+
+    addition_parts_names = model_string.split('+')
+    if len(addition_parts_names) >= 2:
+        addition_parts = [load_model_info(part) for part in addition_parts_names]
+    elif len(addition_parts_names) == 1:
+        product_parts_names = model_string.split('*')
+        if len(product_parts_names) >= 2:
+            product_parts = [load_model_info(part) for part in product_parts_names]
+        elif len(product_parts_names) == 1:
+            kernel_module = generate.load_kernel_module(product_parts_names[0])
+            return modelinfo.make_model_info(kernel_module)
+
+    model = None
+    if len(product_parts) > 1:
+        model = mixture.make_mixture_info(product_parts, operation='*')
+    if len(addition_parts) > 1:
+        if model is not None:
+            addition_parts.append(model)
+        model = mixture.make_mixture_info(addition_parts, operation='+')
+    return model
 
 
Index: sasmodels/mixture.py
===================================================================
--- sasmodels/mixture.py	(revision fb9a3b6f2133421705828b7ea99dcd1e96bfeba3)
+++ sasmodels/mixture.py	(revision 61a4bd4edf4783346bfa77b49e35468862407ac8)
@@ -30,23 +30,83 @@
     Create info block for mixture model.
     """
-    flatten = []
-    for part in parts:
-        if part.composition and part.composition[0] == 'mixture':
-            flatten.extend(part.composition[1])
-        else:
-            flatten.append(part)
-    parts = flatten
-
     # Build new parameter list
     combined_pars = []
     demo = {}
+
+    model_num = 0
+    all_parts = copy(parts)
+    is_flat = False
+    while not is_flat:
+        is_flat = True
+        for part in all_parts:
+            if part.composition and part.composition[0] == 'mixture' and \
+                len(part.composition[1]) > 1:
+                all_parts += part.composition[1]
+                all_parts.remove(part)
+                is_flat = False
+
+    # When creating a mixture model that is a sum of product models (ie (1*2)+(3*4))
+    # the parameters for models 1 & 2 will be prefixed with A & B respectively,
+    # but so will the parameters for models 3 & 4. We need to rename models 3 & 4
+    # so that they are prefixed with C & D to avoid overlap of parameter names.
+    used_prefixes = []
+    for part in parts:
+        i = 0
+        if part.composition and part.composition[0] == 'mixture':
+            npars_list = [info.parameters.npars for info in part.composition[1]]
+            for npars in npars_list:
+                # List of params of one of the constituent models of part
+                submodel_pars = part.parameters.kernel_parameters[i:i+npars]
+                # Prefix of the constituent model
+                prefix = submodel_pars[0].name[0]
+                if prefix not in used_prefixes: # Haven't seen this prefix so far
+                    used_prefixes.append(prefix)
+                    i += npars
+                    continue
+                while prefix in used_prefixes:
+                    # This prefix has been already used, so change it to the
+                    # next letter that hasn't been used
+                    prefix = chr(ord(prefix) + 1)
+                used_prefixes.append(prefix)
+                prefix += "_"
+                # Update the parameters of this constituent model to use the
+                # new prefix
+                for par in submodel_pars:
+                    par.id = prefix + par.id[2:]
+                    par.name = prefix + par.name[2:]
+                    if par.length_control is not None:
+                        par.length_control = prefix + par.length_control[2:]
+                i += npars
+
+    model_num = len(all_parts) - len(parts)
+    if model_num != 0:
+        model_num += 1
+
     for k, part in enumerate(parts):
         # Parameter prefix per model, A_, B_, ...
         # Note that prefix must also be applied to id and length_control
         # to support vector parameters
-        prefix = chr(ord('A')+k) + '_'
+        prefix = chr(ord('A')+k+model_num) + '_'
+        if part.composition and part.composition[0] == 'mixture':
+            # Parameter already has a prefix as it's part of a composition model
+            prefix = ''
+            model_num -= 1
+            
         if operation == '+':
             # If model is a sum model, each constituent model gets its own scale parameter
-            scale =  Parameter(prefix+'scale', default=1.0,
+            scale_prefix = prefix
+            if prefix == '' and part.operation == '*':
+                # `part` is a composition product model. Find the prefixes of
+                # it's parameters to form a new prefix for the scale, eg:
+                # a model with A*B*C will have ABC_scale
+                sub_prefixes = []
+                for param in part.parameters.kernel_parameters:
+                    # Prefix of constituent model
+                    sub_prefix = param.id.split('_')[0]
+                    if sub_prefix not in sub_prefixes:
+                        sub_prefixes.append(sub_prefix)
+                # Concatenate sub_prefixes to form prefix for the scale
+                scale_prefix = ''.join(sub_prefixes) + '_'
+            scale =  Parameter(scale_prefix + 'scale', default=1.0,
                             description="model intensity for " + part.name)
             combined_pars.append(scale)
@@ -67,5 +127,5 @@
     model_info.id = operation.join(part.id for part in parts)
     model_info.operation = operation
-    model_info.name = operation.join(part.name for part in parts)
+    model_info.name = '(' + operation.join(part.name for part in parts) + ')'
     model_info.filename = None
     model_info.title = 'Mixture model with ' + model_info.name
