"""
Contains functionality relate to interfacing wiggly and puq
"""
import binascii,os,re
import numpy as np
import matplotlib.pyplot as plt
import shapely.geometry
import CrispObjects,FuzzyObjects,fuzz,utilities
import _distributions as distributions
import puq

class ObjectManager(object):
    """
    Manages Wiggly objects.

    - *ignore_certain_vertices*: Used in the SA module to limit the number of runs required
      when there are partially certain objects.

      If True, then certain vertices (or edges) which would otherwise be added to puq as
      ConstantParameters are not added at all.

      ================ =============================================================================
      Object Type       Behavior
      ================ =============================================================================
      Rigid            No effect. x,y,theta are included in the SA even if they are certain.

      deformable       Non-uncertain vertices are not included in the SA

      vertex-defined   Non-uncertain vertices are not included in the SA (todo)

      edge-defined     Non-uncertain edges are not included in the SA (todo)
      ================ =============================================================================
    """

    def __init__(self,ignore_certain_vertices=False):


        self._objects={}    #keys are object names, values are dictionaries with keys:values
                            # 'obj':FObject 'group':the group obj belongs to
                            # 'tag':any additional information about obj
        self._groups={}     #keys are group names, values are lists of object names
        self._classes={}    #keys are class names, values are lists of object names

        self._shapesFileFirstWrite=True
        self._ignore_certain_vertices=ignore_certain_vertices

    @staticmethod
    def isShapeParam(paramname):
        """
        Given a parameter name, checks whether this parameter indicates that it belongs
        to a shape.

        - *paramname*: a string indicating the parameter name to check

        Returns: true if this parameter name indicates that it is part of a shape
        """
        if paramname!=None:
            if str(paramname).startswith("wXY__"):
                return True
            else:
                return False
        else:
            return False

    @staticmethod
    def isShapeParamLegacy(paramname):
        """
        Given a legacy parameter name, checks whether this parameter indicates that it belongs
        to a shape.

        - *paramname*: a string indicating the parameter name to check

        Returns: true if this parameter name indicates that it is part of a shape.

        After changing the wiggly parameter separator to __ instead of _, this
        function is needed to check whether the separators should be __ instead of _
        when parsing the parameters into shapes.
        """
        if paramname!=None:
            if str(paramname).startswith("wXY_"):
                return True
            else:
                return False
        else:
            return False

    @staticmethod
    def getBaseShapes(shapesFileName='shapes.json'):
        """
        Constructs shapely shapes out of a previously saved shapes file generated by
        :func:`ObjectManager.crispObjects2PuqParams` or :func:`ObjectManager.fuzzyObjects2PuqParams`.

        Returns a dictionary in the same format as :func:`ObjectManager.puq2Shapes`.
        """
        if not os.path.isfile(shapesFileName):
            utilities.msg('getBaseShapes:{} not found. No shapes generated'.format(shapesFileName),'w')
            return {}

        f=open(shapesFileName,'r')
        baseShapesDict=puq.unpickle(f.read())

        shapes={}

        #search for all params corresponding to this shape
        for shpName,shpItem in baseShapesDict.iteritems():
            shp_rt=None
            if len(shpItem['pts_x'])==1:
                #point
                shp_rt=shapely.geometry.Point(shpItem['pts_x'],shpItem['pts_y'])
            elif shpItem['isClosed']:
                #polygon
                shp_rt=shapely.geometry.Polygon(np.vstack((shpItem['pts_x'],shpItem['pts_y'])).T)
            else:
                #linestring
                shp_rt=shapely.geometry.LineString(np.vstack((shpItem['pts_x'],shpItem['pts_y'])).T)

            if shp_rt!=None:
                shapes[shpName]={'shp':shp_rt,'desc':'base shape','type':shpItem['type']}
        return shapes

    @staticmethod
    def puq2Shapes(shapesFileName='shapes.json',params=None,paramsFileName=None):
        """
        Constructs distorted shapes out of a puq parameters, using a set of base shapes.


        - *shapesFileName*: a file name containing the base shapes. See :func:`crispObjects2PuqParams`
          for more information.
        - *params*: a list of dictionaries. Each dictionary contains the keys 'name',
          'desc', 'value'. Key 'name' is the puq param name constructed using the \*2puqParams methods.
          'desc' (may be None) is the description and 'value' is a single value
          of the parameter.

              [ {'name':<string>, 'desc':<string>, 'value':<float>}, ... ]

          See :func:`crispObjects2PuqParams` for details on the convention for 'name'.

        - *paramsFileName*: If set, *params* is read from file. See the paramsByFile option of
          the puq TestProgram documentation.

        Returns: a dictionary with keys equal to the parameter name. The parameter name is the
        original name, extracted from the name generated by \*2puqParams. Values are dictionaries
        with the following key:value pairs -- 'shp':Shapely shape 'desc':description.

            {<paramName string>, {'shp':<Shapely object>, 'desc':<string>,
            'type':<string>} 'alphacut':<float or None>, ...}

        'type' is the type of object: 'D'=deformable, 'R'=rigid, 'V'=vertex-defined, 'E'=edge-defined.
        Any parameters which weren't constructed with :func:`crispObjects2PuqParams` or
        :func:`fuzzyObjects2PuqParams` are ignored.
        """
        shapes={}

        if paramsFileName!=None:
            if params!=None:
                print('paramsFileName set. Ignoring params')

            #paramvalues is a list of tuples.
            paramValues=np.loadtxt(paramsFileName,
                           dtype={"names":("p_name","p_val","p_desc"),
                                  "formats":(np.object,np.float,np.object)})
            params=[]
            for tupleParam in paramValues:
                params.append({'name':tupleParam[0], 'value':tupleParam[1],
                                   'desc':tupleParam[2]})
        else:
            if params==None:
                raise Exception("Must specify either params or paramsFileName")

        #Parse object metadata from the puq parameter name.
        #data contains the parsed information:
        #key=name value=dictionary with
        #   'index':list of int
        #   'varType':list of string
        #   'alphacut':list of float. (Empty list for probabilistic parameters)
        #   'desc': list of string
        #   'value': list of float
        data={}
        for param in params:
            if param['name']!=None:
                if ObjectManager.isShapeParam(param['name']) or ObjectManager.isShapeParamLegacy(param['name']):
                    if ObjectManager.isShapeParam(param['name']):
                        parse=param['name'].split('__')
                    else:
                        #for files which were made before the switch to double underscores
                        parse=param['name'].split('_')

                    #the parameter has been verified to come from *2PuqParams.
                    #do more validation
                    skipped=False

                    if len(parse)<4 or len(parse)>5:
                        print("Error: parameter {} isn't in the proper format.".format(param['name']))
                        skipped=True
                    elif parse[2]!='x' and parse[2]!='y' and parse[2]!='t' and parse[2]!='e':
                        print("Error: 'varType' must be x, y, or t. Got {}".format(parse[2]))
                        skipped=True
                    else:
                        objName=parse[1]
                        varType=parse[2]
                        index=int(parse[3])

                        acut=None

                        #get the alpha cut for objects that have it
                        if len(parse)==5:
                            acut=float(parse[4])/10.

                        if not skipped:
                            if not objName in data.keys():
                                data[objName]={'index':[],'varType':[],'value':[],'desc':[], 'alphacut':[]}

                            data[objName]['index'].append(index)
                            data[objName]['varType'].append(varType)
                            data[objName]['desc'].append(param['desc'])
                            data[objName]['value'].append(param['value'])
                            data[objName]['alphacut'].append(acut)

                    if skipped:
                        print("Skipped {}".format(param['name']))
                #end if ObjectManager.isShapeParam(param['name'])
            #end if param['name']!=None
        #end for param in params

        #open the file containing the base shapes to distort
        #baseShapes is a dict with keys equal to objectname
        #and values of dictionaries of properties. see shapes.json
        if not os.path.isfile(shapesFileName):
            utilities.msg('puq2shapes:{} not found. No shapes generated'.format(shapesFileName),'w')
            return {}

        f=open(shapesFileName,'r')
        baseShapesDict=puq.unpickle(f.read())

        #search for all params corresponding to this shape
        for shpName,shpItem in baseShapesDict.iteritems():
            shp_rt=None

            if shpItem['type']=='R':
                #rigid object. we're lookgin for 3 variables x,y,theta
                if shpName in data.keys():
                    shp_rt=ObjectManager._puq2Shapes_rigidObjectSingle(shpName,data[shpName],shpItem)
            elif shpItem['type']=='D':
                #deformable object. we're looking for x,y variables for each vertex.
                #building the shape is different since we don't need to use the base shape
                #all the information for constructing a shape is present in the puq parameter
                if shpName in data.keys():
                    shp_rt=ObjectManager._puq2Shapes_deformableObjectSingle(shpName,data[shpName],shpItem)
            elif shpItem['type']=='V':
                #vertex defined object. Same as deformable object for the purpose of
                #building a single shapely shape.
                if shpName in data.keys():
                    shp_rt=ObjectManager._puq2Shapes_vertexObjectSingle(shpName,data[shpName],shpItem)
            elif shpItem['type']=='E':
                #edge-defined object
                if shpName in data.keys():
                    shp_rt=ObjectManager._puq2Shapes_edgeObjectSingle(shpName,data[shpName],shpItem)
            else:
                print('Type {} not supported. Ignoring'.format(shpItem['type']))

            if shp_rt!=None:
                shapes[shpName]={'shp':shp_rt,'desc':data[shpName]['desc'][0],
                                 'type':shpItem['type'],'alphacut':data[shpName]['alphacut'][0]}
        return shapes

    @staticmethod
    def _indices(lst, element):
        result = []
        offset = -1
        while True:
            try:
                offset = lst.index(element, offset+1)
            except ValueError:
                return result
            result.append(offset)

    @staticmethod
    def _puq2Shapes_deformableObjectSingle(objName,objData,baseShapeData):
        """
        Builds a single Shapely shape out of deformable object data.

        See :func:`ObjectManager._puq2Shapes_rigidObjectSingle`
        """
        shp_rt=None

        #do some more validation. The length of all lists must be the same
        #for each object.
        len1=len(objData['index'])
        lens=np.r_[len(objData['varType']),len(objData['value'])]

        if np.any(lens!=len1):
            #the length of the vertex indices must be the same as the length of
            #all the other lists
            print("Error. Can't create object {}. The data was corrupt".format(objName))
        else:
            #ensure that we have the same number of x and y entries using generator comprehension
            count_x=sum(xy=='x' for xy in objData['varType'])
            count_y=sum(xy=='y' for xy in objData['varType'])

            if count_x!=count_y:
                print("Error for {}. There must be the same number of x and y values".format(objName))
            elif count_x!=(len1/2):
                print("Errpr for {}. unexpected count for 'varType'.".format(objName))
            else:
                pts_x,pts_y=ObjectManager._puq2shapes_deformableVertex_buildobject_helper(baseShapeData,
                                                                                          objData,
                                                                                          objName)
                #we now can build the shape
                do=CrispObjects.DeformableObjectFromValues(baseShapeData['pts_x'],baseShapeData['pts_y'],
                                    baseShapeData['uncert_pts'],baseShapeData['isClosed'],
                                    pts_x,pts_y)
                shp_rt= do.realizationsPolygons[0]

        return shp_rt

    @staticmethod
    def _puq2Shapes_rigidObjectSingle(objName,objData,baseShapeData):
        """
        Builds a single Shapely shape out of rigid object data.

        - *objName*: the name of the object
        - *objData*: a dictionary

            {'index':[int], 'varType':[string], 'desc':[string], 'value':[float]}
        - *baseShapeData*: a dictionary.

        Returns a Shapely shape. If there is an error, returns None (unless an exception is raised).

        """
        shp_rt=None

        lens=np.r_[len(objData['varType']),len(objData['value'])]
        if np.any(lens!=3):
            #the length of the vertex indices must be the same as the length of
            #all the other lists
            print("Warning. Can't create object {}. The data was corrupt".format(objName))
        else:
            new_x=np.nan
            new_y=np.nan
            theta=np.nan
            for i in range(3):
                xyt=objData['varType'][i]
                if xyt=='x':
                    new_x=objData['value'][i]
                elif xyt=='y':
                    new_y=objData['value'][i]
                elif xyt=='t':
                    theta=objData['value'][i]
                else:
                    raise Exception('Unexpected value for varType ({}) for rigid object {}'.format(xyt,objName))

            if new_x==np.nan or new_y==np.nan or theta==np.nan:
                raise Exception('rigid object {} is corrupted'.format(objName))

            #now build a skeleton rigid object to hold this realization
            ro=CrispObjects.RigidObjectFromValues(baseShapeData['pts_x'],baseShapeData['pts_y'],
                                baseShapeData['origin'][0],baseShapeData['origin'][1],
                                baseShapeData['uncert_pts'],baseShapeData['isClosed'],
                                np.r_[new_x],np.r_[new_y],np.r_[theta])

            #Get the shape. there should only be 1 anyways
            shp_rt=ro.realizationsPolygons[0]

        return shp_rt

    @staticmethod
    def _puq2Shapes_edgeObjectSingle(objName,objData,baseShapeData):
        """
        Builds a single Shapely shape out of edge-defined object data.

        - *objName*: the name of the object
        - *objData*: a dictionary

            {'index':[int], 'varType':[string], 'desc':[string], 'value':[float], 'alphacut':[float]}
        - *baseShapeData*: a dictionary.

        Returns a Shapely shape. If there is an error, returns None (unless an exception is raised).

        """
        shp_rt=None

        #do some more validation. The length of all lists must be the same
        #for each object.
        len1=len(objData['index'])
        lens=np.r_[len(objData['varType']),len(objData['value']), len(objData['alphacut'])]

        if np.any(lens!=len1):
            #the length of the edge indices must be the same as the length of
            #all the other lists
            print("Warning. Can't create object {}. The data was corrupt".format(objName))
        else:
            if baseShapeData['isClosed']:
                numedges=len(baseShapeData['pts_x'])+1
            else:
                numedges=len(baseShapeData['pts_x'])

            edge_offsets=np.zeros(numedges)*np.nan
            for i in range(numedges):
                idxs=ObjectManager._indices(objData['index'],i)

                if len(idxs)==0:
                    edge_offsets[i]=0
                elif len(idxs)!=1:
                    raise Exception('You should not see this error. Edge-defined object {} had more than one variables associated with index {}'.format(objName,i,))
                else:
                    xyt=objData['varType'][idxs[0]]
                    if xyt=='e':
                        edge_offsets[i]=objData['value'][idxs[0]]
                    else:
                        raise Exception('Unexpected value for varType ({}) for edge-defined object {}'.format(xyt,objName))
                #end if

            #verify that all edges have the same alpha cut
            acut=objData['alphacut'][0] #single alpha cut value
            if any([a!=acut for a in objData['alphacut']]):
                raise Exception('The edges must all have the same alpha cuts')

            #now build a skeleton rigid object to hold this realization
            #since this is a single realization, it only has 1 shape at a single alpha cut.
            samples_dict={acut:edge_offsets}
            edo=FuzzyObjects.EdgeDefinedObjectFromValues(baseShapeData['pts_x'],baseShapeData['pts_y'],
                                baseShapeData['isClosed'],
                                samples_dict)

            #Get the shape. there should only be 1
            shp_rt=edo.realizationsPolygons[acut][0]

        return shp_rt

    @staticmethod
    def _puq2Shapes_vertexObjectSingle(objName,objData,baseShapeData):
        """
        Builds a single Shapely shape out of vertex-defined object data.

        - *objName*: the name of the object
        - *objData*: a dictionary

            {'index':[int], 'varType':[string], 'desc':[string], 'value':[float], 'alphacut':[float]}
        - *baseShapeData*: a dictionary.

        Returns a Shapely shape. If there is an error, returns None (unless an exception is raised).

        """
        shp_rt=None

        #do some more validation. The length of all lists must be the same
        #for each object.
        len1=len(objData['index'])
        lens=np.r_[len(objData['varType']),len(objData['value']), len(objData['alphacut'])]

        if np.any(lens!=len1):
            #the length of the vertex indices must be the same as the length of
            #all the other lists
            print("Warning. Can't create object {}. The data was corrupt".format(objName))
        else:
             #ensure that we have the same number of x and y entries using generator comprehension
            count_x=sum(xy=='x' for xy in objData['varType'])
            count_y=sum(xy=='y' for xy in objData['varType'])

            if count_x!=count_y:
                print("Errpr for {}. There must be the same number of x and y values".format(objName))
            elif count_x!=(len1/2):
                print("Error for {}. unexpected count for 'varType'.".format(objName))
            else:
                pts_x,pts_y=ObjectManager._puq2shapes_deformableVertex_buildobject_helper(baseShapeData,
                                                                                          objData,
                                                                                          objName)

                #verify that all edges have the same alpha cut
                acut=objData['alphacut'][0] #single alpha cut value
                if any([a!=acut for a in objData['alphacut']]):
                    raise Exception('The edges must all have the same alpha cuts')

                #now build a skeleton  object to hold this realization
                #since this is a single realization, it only has 1 shape at a single alpha cut.
                samples_dict={acut:np.hstack((pts_x,pts_y))}
                vdo=FuzzyObjects.VertexDefinedObjectFromValues(baseShapeData['pts_x'],baseShapeData['pts_y'],
                                    baseShapeData['isClosed'],
                                    samples_dict)

                #Get the shape. there should only be 1
                shp_rt=vdo.realizationsPolygons[acut][0]

            #end if

        return shp_rt

    @staticmethod
    def _puq2shapes_deformableVertex_buildobject_helper(baseShapeData, objData,objName):
        #make sure the lists are sorted according to the index so that we build
        #the object in the right order. http://stackoverflow.com/questions/6618515
        # ##DISABLED## not needed anymore
#                objData_sorted=sorted(zip(objData['index'],objData['xORy'],
#                                          objData['isClosed'],objData['value']),key=lambda pair:pair[0])

        numvert=len(baseShapeData['pts_x'])

        #build arrays which will hold the vertices
        pts_x=np.zeros(numvert)*np.nan
        pts_y=np.zeros(numvert)*np.nan

        for i in range(numvert):
            idxs=ObjectManager._indices(objData['index'],i)

            if len(idxs)==0:
                #if we're here, the ith vertex of baseshape was not found in the list
                #of uncertain vertices. Therefore it must be certain. replace it with
                #the corresponding vertex of baseshape
                pts_x[i]=baseShapeData['pts_x'][i]
                pts_y[i]=baseShapeData['pts_y'][i]
            elif len(idxs)!=2:
                raise Exception('You should not see this error. Deformable object {} had more than two variables associated with index {}'.format(objName,i,))

            for idx in idxs:
                #insert the x or y value into the correct location in the list.
                #due to the above checks, there is guaranteed to be the same
                #number of x and y values
                xyt=objData['varType'][idx]
                if xyt=='x':
                    pts_x[i]=objData['value'][idx]
                elif xyt=='y':
                    pts_y[i]=objData['value'][idx]
                else:
                    raise Exception('Unexpected value for varType ({}) for deformable object {}'.format(xyt,objName))
            #end for

        return pts_x,pts_y

    def addObject(self,obj,name=None,data=None):
        """
        Adds a CrispObject or FuzzyObject to the manager.

        - *obj*: The object to add. The object must have been previously initialized
          and realizations generated.
        - *name*: A string which identifies the object. Must consist of letters and numbers
          ONLY. Allowed names follow the restrictions for valid Python variable names.
          If not specified, a name will be
          generated automatically. If specified, it must be unique else, an error will
          be generated.
        - *data*: Any optional extra data to be stored with the object.

        When adding multiple fuzzy objects (by subsequent calls to this function), all
        objects must have the same alpha cuts.
        """
        if name!=None and name!="":
            #don't allow name with special characters. Also
            reg=re.compile(r'^[^a-zA-Z_]')
            if bool(reg.search(name)):
                raise Exception('{} is not valid. Name must start with a letter'.format(name))
            reg=re.compile(r'__+')
            if bool(reg.search(name)):
                raise Exception('{} is not valid. Double underscores are reserved.'.format(name))
            reg=re.compile(r'\W')
            if bool(reg.search(name)):
                raise Exception('{} is not valid. Name can only contain letters, numbers and _'.format(name))


        name=self._getNextKey(self._objects) if name==None or name=="" else name
        if obj._lastDistr==None:
                raise Exception('Object not initialized. Must generate realizations first')

        if utilities.isCrispObject(obj):
            cls='probabilistic'
        elif utilities.isFuzzyObject(obj):
            cls='fuzzy'

            #check to make sure they all have the same alpha cuts
            if cls in self._classes.keys():
                for fuzzyobjname,managedfuzzyobj in self._objects.iteritems():
                    if managedfuzzyobj['class']==cls:
                        if not all([cut in managedfuzzyobj['obj'].realizations.keys() for cut in obj.realizations.keys()]):
                            raise Exception('All fuzzy objects must have the same alpha cuts')
        else:
            raise Exception ("unrecognized type " + str(type(obj)))

        try:
            #add to dictionary. object is stored by reference
            self._objects[name]={'obj':obj, 'class':cls, 'data':data}
            if cls in self._classes.keys():
                #if group exists, add obj to it
                self._classes[cls].append(name)
            else:
                self._classes[cls]=[name]
        except Exception,e:
            raise Exception('Error adding object of type:{} '.format(cls) + str(e))

    @property
    def objects(self):
        """
        Returns the objects currently managed by ObjectManager.

        Objects are returned in the form of a dictionary with keys equal to the
        object names and values containing dictionaries with keys 'obj' (the Wiggly
        object being managed), 'class' (the group assigned to obj), and 'data'.

            {$name$:{'obj':<shapely shape>}, 'class':<string>, 'data':<object>}

        'class' can be 'probabilistic' or 'fuzzy'.
        """
        return self._objects

    @property
    def probabilisticObjects(self):
        """
        Returns a list of all the CrispObjects that have been added via :func:`addObject`.

        To get the object's extra data use :attr:`objects`
        """
        if not 'probabilistic' in self._classes:
            return []
        return [self._objects[obj]['obj'] for obj in self._classes['probabilistic']]

    @property
    def fuzzyObjects(self):
        """
        Returns a list of all the FuzzyObjects that have been added via :func:`addObject`.

        To get the object's extra data use :attr:`objects`
        """
        if not 'fuzzy' in self._classes:
            return []
        return [self._objects[obj]['obj'] for obj in self._classes['fuzzy']]


    def crispObjects2PuqParams(self,shapesFileName='shapes.json',use_samples=True):
        """
        Converts crisp (probabilistic) objects into puq Parameter objects.

        - *shapesFileName*: a file name which holds the base shapes.
        - *use_samples*: If True, uses the FObject's previously generated realizations (default).
          If the object element (ie, vertex, edge etc) is not a constant, a puq CustomParameter
          object is returned with it's values property set to the realizations.
          If False, an appropriate puq Parameter object is returned, with its values property set
          to an empty list. E.g., for a normally distributed DeformableObject, NormalParameter
          objects are returned. Unlike the CustomParameters generated if this flag is False,
          these Parameter objects will be sampled by puq.

        Returns: a list of puq CustomParameter or ConstantParameter objects.

        The name of each Parameter is based on the name given to the associated
        object when it was added in addObject. For example, for an object named
        'house', the x-coordinate of
        the third vertex of the object is encoded using the following convention:

            wXY__house__x__2

        All parameters constructed with this function are identified by the first 5 characters:
        'wXY\_\_'

        The total number of Parameters contributed to the returned list can
        be determined from the table:

        =================== =====================
        Object Type         Num. Parameters
                            contributed
        =================== =====================
        DeformableObject    2N - one for each of
                            the x,y coordinate
                            pairs for the N
                            vertices.
        RigidObject         3 - x, y, and theta
        =================== =====================

        For example, if the ObjectManager contains a single RigidObject, calling
        this function will return a list of 3 custom parameters and the base shape
        of the rigid object will be written to *shapesFileName*
        """

        if shapesFileName==None:
            raise Exception("must specify a file name which will hold the shapes to process")
        if len(self._objects)==0:
            raise Exception("There are no objects to convert")

        shapes={}

        puq_params=[]
        for objName in self._classes['probabilistic']:
            managedObj=self._objects[objName]

            obj=managedObj['obj']
            isClosed=obj.isClosed
            uncert_pts=obj.uncertainVertices

            objtype=None
            origin=None
            if utilities.isRigidObject(obj):
                desc='rigid object coord'
                objtype='R'

                #get the data from the rigid object
                x_samples,y_samples,t_samples=obj.realizationsParams
                origin=obj.origin
                data={'x':x_samples, 'y':y_samples, 't':t_samples}


                var=['x','y','t']
                for i,v in enumerate(var):
                    name='wXY__{}__{}__{}'.format(objName,v,0)
                    if np.all(data[v]==data[v][0]):
                        #if all the samples for this variable are the same, it is a constant
                        param=puq.ConstantParameter(name,'[C] ' + desc,attrs=[('uncert_type','prob-const')],
                                                    value=np.r_[data[v][0]])
                    else:
                        if use_samples:
                            param=puq.CustomParameter(name,desc,attrs=[('uncert_type','prob')],
                                                      pdf=data[v],use_samples_val=True)
                        else:
                            param=self._element2SpecificPuqParam(name,desc,obj,i)

                    if param!=None:
                        puq_params.append(param)

            elif utilities.isDeformableObject(obj):
                desc='deformable object coord.'
                objtype='D'

                #get the data from the object
                pts_x_samples,pts_y_samples=obj.realizations

                for i in range(np.size(pts_x_samples,1)):
                    #for the x,y coord of each vertex (column of pts_<>_samples),
                    #create a parameter. set the parameter values to the
                    #samples (the rows)
                    name='wXY__{}__x__{}'.format(objName,i)
                    if np.all(pts_x_samples[:,i]==pts_x_samples[0,i]):
                        #if all the samples for this variable are the same, it is a constant
                        param=puq.ConstantParameter(name,'[C] ' + desc,attrs=[('uncert_type','prob-const')],
                                                    value=np.r_[pts_x_samples[0,i]])
                        if self._ignore_certain_vertices:
                            param=None
                    else:
                        if use_samples:
                            param=puq.CustomParameter(name,desc,attrs=[('uncert_type','prob')],
                                                      pdf=pts_x_samples[:,i],use_samples_val=True)
                        else:
                            param=self._element2SpecificPuqParam(name,desc,obj,i)

                    if param!=None:
                        puq_params.append(param)

                    name='wXY__{}__y__{}'.format(objName,i)
                    if np.all(pts_y_samples[:,i]==pts_y_samples[0,i]):
                        param=puq.ConstantParameter(name,'[C] ' + desc,attrs=[('uncert_type','prob-const')],
                                                    value=np.r_[pts_y_samples[0,i]])
                        if self._ignore_certain_vertices:
                            param=None
                    else:
                        if use_samples:
                            param=puq.CustomParameter(name,desc,attrs=[('uncert_type','prob')],
                                                      pdf=pts_y_samples[:,i],use_samples_val=True)
                        else:
                            param=self._element2SpecificPuqParam(name,desc,obj,np.size(pts_x_samples,1)+i)

                    if param!=None:
                        puq_params.append(param)
            else:
                raise Exception("object of type {} not supported".format(str(type(obj))))

            shapes[objName]={'type':objtype,'pts_x':obj.coords[0], 'pts_y':obj.coords[1],
                             'isClosed':isClosed,'origin':origin, 'uncert_pts':uncert_pts}

        #end for: objName in self._classes['probabilistic']

        self._writeShapesFile(shapes,shapesFileName)

        return puq_params

    def fuzzyObjects2PuqParams(self,shapesFileName='shapes.json',use_samples=True):
        """
        Converts fuzzy objects into puq Parameter objects.

        - *shapesFileName*: a file name to which the base shapes will be written.
        - *use_samples*: If True, uses the FObject's previously generated realizations (default).
          If the object element (ie, vertex, edge etc) is not a constant, a puq CustomParameter
          object is returned with it's values property set to the realizations.
          If False, an appropriate puq Parameter object is returned, with its values property set
          to an empty list. E.g., for a normally distributed DeformableObject, NormalParameter
          objects are returned. Unlike the CustomParameters generated if this flag is False,
          these Parameter objects will be sampled by puq.

        Returns a dictionary with keys equal to alpha cuts of the objects. The values
        are dictionaries with keys 'params', 'num_realizations'

            { $a-cut$:{'params':[object], 'num_realizations':integer}, ...}

        'params' is a list of puq CustomParameter or ConstantParameter objects each containing
        samples of vertex coordinates or edges at that
        particular alpha cut. **All non-constant parameters at a given a-cut are guaranteed to have
        the same number of realizations.** This number is the maximum number of realizzations of
        all objects at that alpha cut and is given in the
        'num_realizations' entry. If a parameter is a constant, it will have 1 realization.

        For example at alpha-cut 0.2, O1 has 10 realizations and O2 has 8.
        The generated puq parameters for O1 will all have 10 realizations. For O2, the number of
        realizations will be increased to 10 by repeating the first two samples. If O2 is a constant
        instead, the number of realizations in the puq parameter is 1.

        All fuzzy objects are guaranteed to have the same alpha cuts.

        The name of each Parameter is based on the name given to the associated
        object when it was added in addObject. For example, for an object named
        'house', the x-coordinate of
        the third vertex of the object  at alpha cut 1 is encoded using the following convention:

            wXY__house__x__2__10

        For an edge defined object, third edge alpha cut 0.8,

            wXY__house__e__2__8

        All parameters constructed with this function are identified by the first 5 characters
        'wXY\_\_'.

        The total number of Parameters contributed to the returned dictionary for a single alpha-cut
        can be determined from the table:

        =================== =====================
        Object Type         Num. Parameters
                            contributed
        =================== =====================
        VertexDefinedObject 2N - one for each of
                            the x,y coordiate
                            pairs for the N
                            vertices.
        EdgeDefinedObject   N - one for each of
                            the N edges of the
                            object.
        =================== =====================
        """
        if shapesFileName==None:
            raise Exception("must specify a file name which will hold the shapes to process")
        if len(self._objects)==0:
            raise Exception("There are no objects to convert")

        shapes={}
        puq_params={}

        #in addObject we verified that all fuzzy objects have the same alpha cuts.
        #Get a list of alpha cuts from the first object
        acuts=self.fuzzyObjects[0].realizations.keys()

        #loop over the alpha cuts. for each alpha cut, determine which object
        #has the most realizations. For objects which have fewer realizations than
        #this number, replicate the realizations so that they all have the same number.
        for acut in acuts:
            maxrealizations=0
            for objName in self._classes['fuzzy']:
                managedObj=self._objects[objName] #dictionary. see addObject
                obj=managedObj['obj'] #FObject
                if len(obj.getRealizations4Sim(acut))>maxrealizations:
                    maxrealizations=len(obj.getRealizations4Sim(acut))
                #print(acut,objName,len(obj.getRealizations4Sim(acut)))

            #print('maxrealizations',maxrealizations)

            #holds the puq params at this alpha level
            puq_params_alpha=[]

            for objName in self._classes['fuzzy']:
                managedObj=self._objects[objName] #dictionary. see addObject

                obj=managedObj['obj'] #FObject
                isClosed=obj.isClosed
                objtype=None

                #list of numpy arrays
                #Each array is guaranteed to have the same length
                realizations=obj.getRealizations4Sim(acut)

                #fuzzyVars: columns are variables, rows realizations
                #the order is defined in the constructor of VertexDefinedObject.
                #The order is first all the x variables, then the y variables.
                fuzzyVars=np.vstack(realizations)
                nFuzzyVars=np.size(fuzzyVars,1)
                numrealizations_this=np.size(fuzzyVars,0)
                #print(acut,objName,np.size(fuzzyVars,0))

                if numrealizations_this<maxrealizations:
                    #if this object has fewer realizations than the maximum out of
                    #all fuzzy objects, expand the fuzzyVars matrix to make up the difference
                    #by copying the required values from the top
                    fuzzyVars=np.take(fuzzyVars,range(maxrealizations),axis=0,mode='wrap')
                    print('Fuzzy var {} had n={} realiz. at a-cut {}. Adjusted to n={}'.format(
                            objName,numrealizations_this,acut,maxrealizations) )

                #this fuzzy variable now has the same number of realizations as the others.
                #We can now process it.

                if utilities.isVertexDefinedObject(obj):
                    desc='vertex-defined obj coord @a-cut {}'.format(acut)
                    objtype='V'

                    var=['x','y']

                    if nFuzzyVars%2!=0:
                        raise Exception('Vertex-defined object must have the same number of x and y variables')

                    for i in range(nFuzzyVars/2):
                        for k in range(len(var)):
                            name='wXY__{}__{}__{}__{}'.format(objName,var[k],i,format(acut*10,'.0f'))
                            samples=fuzzyVars[:,i+nFuzzyVars/2*k]
                            if np.all(samples==samples[0]):
                                #if all the samples for this variable are the same, it is a constant
                                param=puq.ConstantParameter(name,'[C] ' + desc,attrs=[('uncert_type','fuzzy-const')],
                                                            value=np.r_[samples[0]])

                                if self._ignore_certain_vertices:
                                    param=None
                            else:
                                if use_samples:
                                    param=puq.CustomParameter(name,desc,attrs=[('uncert_type','fuzzy')],
                                                              pdf=samples,use_samples_val=True)
                                else:
                                    #print(name)
                                    param=self._element2SpecificPuqParam(name,desc,obj,(nFuzzyVars/2)*k+i,acut)

                            if param!=None:
                                puq_params_alpha.append(param)
                        #end for k in range(len(var))
                    #end for i in range(nFuzzyVars/2)

                elif utilities.isEdgeDefinedObject(obj):
                    desc='edge-defined obj edge offset @a-cut {}'.format(acut)
                    objtype='E'

                    for i in range(nFuzzyVars):
                        name='wXY__{}__e__{}__{}'.format(objName,i,format(acut*10,'.0f'))
                        samples=fuzzyVars[:,i]
                        if np.all(samples==samples[0]):
                            #if all the samples for this variable are the same, it is a constant
                            param=puq.ConstantParameter(name,'[C] ' + desc,attrs=[('uncert_type','fuzzy-const')],
                                                        value=np.r_[samples[0]])

                            if self._ignore_certain_vertices:
                                param=None
                        else:
                            if use_samples:
                                    param=puq.CustomParameter(name,desc,attrs=[('uncert_type','fuzzy')],
                                                              pdf=samples,use_samples_val=True)
                            else:
                                param=self._element2SpecificPuqParam(name,desc,obj,i,acut)

                        if param!=None:
                            puq_params_alpha.append(param)
                else:
                    raise Exception("object of type {} not supported".format(str(type(obj))))

                shapes[objName]={'type':objtype,'pts_x':obj.coords[0], 'pts_y':obj.coords[1],
                                 'isClosed':isClosed,'origin':None, 'uncert_pts':None}

            #end for: objName in self._classes['fuzzy']

            puq_params[acut]={'params':puq_params_alpha, 'num_realizations':maxrealizations}
        #end for: acut in acuts

        self._writeShapesFile(shapes,shapesFileName)
        return puq_params

    def _element2SpecificPuqParam(self,name,desc,obj,i,alpha=None):
        """
        Helper function to convert an FObject variable to a puq parameter of a certain type.

        The returned puq parameter can be fed to puq which will sample it.

        - *name,desc*: the parameter name and description
        - *obj*: the object to which the element to be converted belongs
        - *i*: the index of the element to convert. This varies depending on the object type
          e.g., for a deformable object, index 0 converts the x coordinate of the first vertex.
        - *alpha*: the alpha cut to process (only for fuzzy *obj*)

        Note this function does not take into consideration elements that are certain. Such
        elements are treated as uncertain. Therefore the conversion of these elements must
        take place outside this function.
        """
        if utilities.isCrispObject(obj):
            if obj._lastDistr==distributions.DIST_NORM:
                param=puq.NormalParameter(name,desc,attrs=[('uncert_type','prob')],
                                          mean=obj._means[i],dev=np.sqrt(obj._variances[i]))
                param.values=[]
            elif obj._lastDistr==distributions.DIST_UNIF:
                param=puq.UniformParameter(name,desc,attrs=[('uncert_type','prob')],
                                           min=obj._bound_lower[i],max=obj._bound_upper[i])
                param.values=[]
            else:
                raise Exception('Distribution of type {} not supported'.format(obj._lastDistr))
        elif utilities.isFuzzyObject(obj):
            bound_lower=obj._fuzzyVariables[i].alpha(alpha)[0]
            bound_upper=obj._fuzzyVariables[i].alpha(alpha)[1]
            param=puq.UniformParameter(name,desc,attrs=[('uncert_type','fuzzy')],
                                       min=bound_lower,max=bound_upper)
            param.values=[]
        else:
            raise Exception('_element2SpecificPuqParam: obj type not supported')

        return param
    def _writeShapesFile(self,shapes,shapesFileName):
        """
        writes the auxiliary shapes file used to reconstruct the shapes.

        - *shapes*: a dictionary.
        - *shapesFileName*: the file to write
        """
        existingshapes={}
        try:
            if not self._shapesFileFirstWrite:
                #only try to read from an existing file if we've already written to it.
                #This avoids reading from a file left over from a previous run
                f=open(shapesFileName,'r')
                existingshapes=puq.unpickle(f.read())
                f.close()
        except IOError:
            pass

        f=open(shapesFileName,'w')

        existingshapes.update(shapes)

        s=puq.jpickle.pickle(existingshapes)
        f.write(s)
        f.close()

        self._shapesFileFirstWrite=False

    def _getNextKey(self,d):
        """
        Gets a unique key for the dictionary *d* consisting of a 16 character hex value.
        """
        newkey=binascii.hexlify(os.urandom(8))
        i=0
        while newkey in d.keys():
            newkey=binascii.hexlify(os.urandom(8))
            i+=1
            if i>1000:
                raise Exception("Couldn't find unique id")
        return newkey


def test_addFuzzyObjects():
    plt.close('all')

    n=10
    np.random.seed(96931694)
    method='random'

    #method='reducedtransformation' #only for plotting. setting this will fail the assertion below
    #n=None

    #acuts=np.linspace(0,1,num=11)
    acuts=np.r_[0,0.5,1]   #fewer for clarity

    #define a polygon
    pt_x=np.r_[480.,485,520,510,520]
    pt_y=np.r_[123.,100,105,110,117]

    #######
    #edge defined
    #define fuzzy numbers for all the edges.
    #trapezoidal fuzzy numbers are in the form
    #   (kernel_lower,kernel_upper), (support_lower,support_upper)
    edgeMembFcn=[fuzz.TrapezoidalFuzzyNumber((0, 0), (0, 0)),
                 fuzz.TrapezoidalFuzzyNumber((0, 0), (-3, 3)),
                 fuzz.TrapezoidalFuzzyNumber((-1.5, 1.5), (-5, 7)),
                 fuzz.TrapezoidalFuzzyNumber((-1, 1), (-3, 3)),
                 fuzz.TrapezoidalFuzzyNumber((-.75, .75), (-1, 1))]

    edo=FuzzyObjects.EdgeDefinedObject(pt_x,pt_y,edgeMembFcn,isClosed=True)
    edo.generateRealizations(n,acuts,method)
    #edo.plot()
    #edo.plotFuzzyNumbers()

    ###########
    #vertex defined
    membFcn_x=[  fuzz.TrapezoidalFuzzyNumber((0, 0), (0, 0)),
                 fuzz.TrapezoidalFuzzyNumber((0, 0), (-2, 2)),
                 fuzz.TrapezoidalFuzzyNumber((-2, 2), (-2, 2)),
                 fuzz.TrapezoidalFuzzyNumber((-1, 1), (-1.5, 3)),
                 fuzz.TrapezoidalFuzzyNumber((-0.5, 0.5), (-2, 1))]

    #test a point
    membFcn_x=[  fuzz.TrapezoidalFuzzyNumber((-0.5, 0.5), (-1.5, 1.5))]
    pt_x=np.r_[480.]
    pt_y=np.r_[123.]

    membFcn_y=membFcn_x

    vdo=FuzzyObjects.VertexDefinedObject(pt_x+50,pt_y,membFcn_x,membFcn_y,isClosed=False)
    vdo.generateRealizations(n,acuts,method)
    vdo.plot()
    vdo.plotFuzzyNumbers()

    manager=ObjectManager(ignore_certain_vertices=True)
    #manager.addObject(edo,name='edo1')
    manager.addObject(vdo,name='vdo1')

    print('objects: ' + str(manager.objects))
    print('convert to puq params:')

    baseshapesfile='shapes.json'
    print('base shapes in {}'.format(baseshapesfile))
    s_all={}

    puqparams_all=manager.fuzzyObjects2PuqParams(baseshapesfile)
    #puqparams_all=manager.fuzzyObjects2PuqParams(baseshapesfile,use_samples=False)

    for acut,puqparams_data in puqparams_all.iteritems():
        print("alpha cut {}".format(acut))

        puqparams=puqparams_data['params']

        s=[]
        oldname=''
        for puqparam in puqparams:
            name=puqparam.name.split('__')[1]
            vertex=int(puqparam.name.split('__')[3])
            var=puqparam.name.split('__')[2]
            if name!=oldname:
                print('  {}'.format(name))
                oldname=name
            print('\tn:{} nm:{} desc:{} \n\t\t{}'.format(np.size(puqparam.values),puqparam.name,puqparam.description,type(puqparam)))

            #add the name, desc, and values
            s.append({'name':puqparam.name,'desc':puqparam.description,'value':puqparam.values})

            #check to make sure the order of the realizations matches after converting to puq params
            #see issue #78. The check only works if method=random
            if name=='edo1':
                assert method=='random','only random realizations supported'
                #use the min since puqparams.values can have more or less entries than getrealizations4sim
                # see documentation of fuzzyObjects2puqParams
                for i in range(min(np.size(puqparam.values),np.size(edo.getRealizations4Sim(acut)[:,vertex]))):
                    assert(puqparam.values[i]==edo.getRealizations4Sim(acut)[i,vertex])
            if name=='vdo1':
                assert method=='random','only random realizations supported'
                rlz=vdo.getRealizations4Sim(acut)
                x=rlz[:,vertex]
                y=rlz[:,(np.size(rlz,1)/2)+vertex]
                if var=='x':
                    for i in range(min(np.size(puqparam.values),np.size(x))):
                        assert(puqparam.values[i]==x[i])
                if var=='y':
                    for i in range(min(np.size(puqparam.values),np.size(y))):
                        assert(puqparam.values[i]==y[i])


        s_all[acut]=s
    plt.show()
    plt.figure()
    return s_all,baseshapesfile,n,[edo,vdo]

def test_fuzzyObjectsFromPuq():
    #tests re-building shapely shapes from puq parameters

    params_all_acuts,baseshapesfile,n,objects=test_addFuzzyObjects()
    print('')

    cm=plt.get_cmap('jet')

    sorted_acuts=sorted(params_all_acuts.keys())
    for acut in sorted_acuts:
        params_all=params_all_acuts[acut]

        ac=0.05 if acut==0 else acut
        clr=cm(ac*0.999999)

        maxrealizations=0
        for param in params_all:
            if np.size(param['value'])>maxrealizations:
                maxrealizations= np.size(param['value'])

        for i in range(maxrealizations):
            params=[]
            for j,param in enumerate(params_all):
                #the % is needed for 'value' in order to handle constants which only have 1 realization.
                params.append({'name':param['name'], 'desc':param['desc'],
                               'value':param['value'][i%np.size(param['value'])]})

            shapes=ObjectManager.puq2Shapes(baseshapesfile,params=params)
            print("acut {} realization {}, number of shapes: {}".format(acut,i,len(shapes)))
            for shpName,shpData in shapes.iteritems():
                shp=shpData['shp']
                shpDesc=shpData['desc']
                if utilities.isShapelyPoint(shp):
                    xy=shp.coords
                    plt.plot(xy[0][0],xy[0][1],'o',color=clr)
                elif utilities.isShapelyLineString(shp):
                    xy=np.asarray(shp)
                    plt.plot(xy[:,0],xy[:,1],'-',color=clr)
                elif utilities.isShapelyPolygon(shp):
                    xy=np.asarray(shp.exterior)
                    plt.plot(xy[:,0],xy[:,1],'-',color=clr)
                    #testing issue #73. i%l is needed because when combining objects, the maxrealizations
                    #is the largers number of realizations out of all the objects.
                    if shpName=='edo1':
                        l=len(objects[0].realizationsPolygons[acut])
                        assert(np.all(xy==np.asarray(objects[0].realizationsPolygons[acut][i%l].exterior)))
                    elif shpName=='vdo1':
                        pass
                else:
                    print("error: unsupported shapely type: {}".format(str(type(shp))))

                print("\tshpname {}\n\t\tpuq param desc: {}".format(shpName,shpDesc))
            print('----')
        print('\n\n')
    plt.axis('equal')
    plt.show()

def test_addCrispObjects():
    n=7
    plt.close('all')

    ########
    #build rigid object

    #define a polygon
    pt_x=np.r_[480.,485,520,510,520]
    pt_y=np.r_[123.,100,105,110,117]

    uncertain_pts=np.r_[1,1,1,0,1]

    centroid_x=np.mean(pt_x)
    centroid_y=np.mean(pt_y)

    centroid_xv=2.
    centroid_yv=2.
    theta_mn=0.
    theta_v=10
    variances=np.r_[centroid_xv,centroid_yv,theta_v]

    #x,y correlated but uncorrelated to theta
    cor3=np.r_[[
                #x 	 	y 	 	theta
                [1,		0.8,		0],	#x
                [0.8,		1,		0],	#y
                [0,		0,		1]	#theta
            ]]

    cov=utilities.cor2cov(cor3,variances)
    ro=CrispObjects.RigidObject(pt_x,pt_y,centroid_x,centroid_y,theta_mn,cov,uncertain_pts)
    ro.generateNormal(n,translate=False,rotate=True)
    #ro.plot(1)
    #########

    #########
    #build deformable object

    #define a point
    pt_x=np.r_[495]
    pt_y=np.r_[110]

    uncertain_pts=[1]

    xv=20
    yv=10
    variances=np.r_[xv,yv]
    cor=np.r_[[
        #x0  #y0
        [1, 0], #x0
        [0, 1], #x1
    ]]

    cov=utilities.cor2cov(cor,variances)
    do1=CrispObjects.DeformableObject(pt_x,pt_y,cov,uncertain_pts,isClosed=False)
    do1.generateUniform(n)
    #do1.plot(1,points=True)

    #define a line
    pt_x=np.r_[488,502]
    pt_y=np.r_[107,107]

    uncertain_pts=[0,1]
    variances=[5,5,5,5]
    cor=np.r_[[
        #x0     x1      y0      y1
        [1,     0,      0,       0], #x0
        [0,     1,      0,       0], #x1
        [0,     0,      1,       0], #y0
        [0,     0,      0,       1], #y1
    ]]
    cov=utilities.cor2cov(cor,variances)
    do2=CrispObjects.DeformableObject(pt_x,pt_y,cov,uncertain_pts,isClosed=False)
    do2.generateUniform(n)
    #do2.plot(1)

    #plt.show()
    #########

    #manager=ObjectManager()
    manager=ObjectManager(ignore_certain_vertices=True)
    manager.addObject(ro,'ro')
    manager.addObject(do1,'do1')
    manager.addObject(do2,'do2')

    print('objects: ' + str(manager.objects))
    print('convert to puq params:')

    baseshapesfile='shapes.json'
    print('base shapes in {}'.format(baseshapesfile))

    s=[]
    oldname=''
    puqparams=manager.crispObjects2PuqParams(baseshapesfile)
    #puqparams=manager.crispObjects2PuqParams(baseshapesfile,use_samples=False)
    for puqparam in puqparams:
        name=puqparam.name.split('__')[1]
        if name!=oldname:
            print('  {}'.format(name))
            oldname=name
        print('\tn:{} nm:{} desc:{} \n\t\t{}'.format(np.size(puqparam.values),puqparam.name,puqparam.description,type(puqparam)))
        #print('\t{}'.format(puqparam.values))

        #add the name, desc, and values
        s.append({'name':puqparam.name,'desc':puqparam.description,'value':puqparam.values})

    #check to make sure the order of the generated realuzations is the same after
    #converting to puq parameters
    #see issue #78
    for puqparam in puqparams:
        name=puqparam.name.split('__')[1]
        var=puqparam.name.split('__')[2]
        if name=='ro':
            x,y,t=ro.realizationsParams
            if var=='x':
                for i in range(np.size(puqparam.values)):
                    assert(x[i]==puqparam.values[i])
            if var=='y':
                for i in range(np.size(puqparam.values)):
                    assert(y[i]==puqparam.values[i])
            if var=='t':
                for i in range(np.size(puqparam.values)):
                    assert(t[i]==puqparam.values[i])
        if name=='do1' or name=='do2':
            vertex=int(puqparam.name.split('__')[3])
            if name=='do1':
                x=do1.realizations[0]
                y=do1.realizations[1]
            else:
                #print(do2.realizations)
                x=do2.realizations[0]
                y=do2.realizations[1]
            if var=='x':
                for i in range(np.size(puqparam.values)):
                    assert(x[i,vertex]==puqparam.values[i])
            if var=='y':
                for i in range(np.size(puqparam.values)):
                    assert(y[i,vertex]==puqparam.values[i])

    return s,baseshapesfile,n,[ro,do1,do2]

def test_crispObjectsFromPuq():
    #tests re-building shapely shapes from puq parameters
    #note: this function only works when manager.crispObjects2PuqParams use_samples=True
    #  in test_addCrispObjects

    plt.close('all')
    params_all,baseshapesfile,n,objects=test_addCrispObjects()
    print('')

    for i in range(n): #n is number of realizations.
        params=[]
        for j,param in enumerate(params_all):
            #the % is needed for 'value' in order to handle constants which only have 1 realization.
            params.append({'name':param['name'], 'desc':param['desc'],
                           'value':param['value'][i%np.size(param['value'])]})

        print(params)
        shapes=ObjectManager.puq2Shapes(baseshapesfile,params=params)
        print("realization {}, number of shapes: {}".format(i,len(shapes)))
        for shpName,shpData in shapes.iteritems():
            shp=shpData['shp']
            shpDesc=shpData['desc']
            if utilities.isShapelyPoint(shp):
                xy=shp.coords
                plt.plot(xy[0][0],xy[0][1],'o')
                assert(np.all(xy==np.asarray(objects[1].realizationsPolygons[i].coords)))
            elif utilities.isShapelyLineString(shp):
                xy=np.asarray(shp)
                plt.plot(xy[:,0],xy[:,1],'-')
                #assert(np.all(xy==np.asarray(objects[2].realizationsPolygons[i]))) #assert will fail when use_samples=True in test_addCrispObjects
            elif utilities.isShapelyPolygon(shp):
                xy=np.asarray(shp.exterior)
                plt.plot(xy[:,0],xy[:,1],'-')
                #check that the realizations after using puq2shapes are in the same order
                #as the original object. See #78
                #  object[0] is the rigid object polygon
                assert(np.all(xy==np.asarray(objects[0].realizationsPolygons[i].exterior)))
            else:
                print("error: unsupported shapely type: {}".format(str(type(shp))))

            print("\tshpname {}\n\t\tdesc {}".format(shpName,shpDesc))
    plt.show()




#check if we're executing this script as the main script
if __name__ == '__main__':
    np.random.seed(93113488)

    #test_addCrispObjects()
    #test_crispObjectsFromPuq()
    #test_addFuzzyObjects()
    test_fuzzyObjectsFromPuq()