Index: sansrealspace/src/realspace/VolumeCanvas.py
===================================================================
--- sansrealspace/src/realspace/VolumeCanvas.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/VolumeCanvas.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,553 @@
+#!/usr/bin/env python
+""" Volume Canvas
+    Simulation canvas
+"""
+
+from sans.models.BaseComponent import BaseComponent
+from sansModeling.pointsmodelpy import pointsmodelpy
+from sansModeling.geoshapespy import geoshapespy
+
+import os.path
+
+class ShapeDescriptor:
+    """
+        Class to hold the information about a shape
+    """
+    def __init__(self):
+        """
+            Initialization
+        """
+        ## Real space object
+        self.shapeObject = None
+        ## Parameters of the object
+        self.params = {}
+        self.params["center"] = [0, 0, 0]
+        self.params["orientation"] = [0, 0, 0]
+        # Default to lores shape
+        self.params['is_lores'] = True
+        self.params['order'] = 0
+            
+    def create(self):
+        """
+            Create an instance of the shape
+        """
+        # Set center
+        x0 = self.params["center"][0]
+        y0 = self.params["center"][1]
+        z0 = self.params["center"][2]
+        geoshapespy.set_center(self.shapeObject, x0, y0, z0)
+        
+        # Set orientation
+        x0 = self.params["orientation"][0]
+        y0 = self.params["orientation"][1]
+        z0 = self.params["orientation"][2]
+        geoshapespy.set_orientation(self.shapeObject, x0, y0, z0)
+               
+class SphereDescriptor(ShapeDescriptor):
+    """
+        Descriptor for a sphere
+    """
+    def __init__(self):
+        """
+            Initialization
+        """ 
+        ShapeDescriptor.__init__(self)
+        # Default parameters
+        self.params["type"]   = "sphere"
+        self.params["radius"] = 20.0
+        self.params["contrast"] = 1.0
+
+    def create(self):
+        """
+            Create an instance of the shape
+            @return: instance of the shape
+        """
+        self.shapeObject = geoshapespy.new_sphere(\
+            self.params["radius"])
+        
+        ShapeDescriptor.create(self)   
+        return self.shapeObject
+    
+class CylinderDescriptor(ShapeDescriptor):
+    """
+        Descriptor for a cylinder
+    """
+    def __init__(self):
+        """
+            Initialization
+        """ 
+        ShapeDescriptor.__init__(self)
+        # Default parameters
+        self.params["type"]   = "cylinder"
+        self.params["length"] = 40.0
+        self.params["radius"] = 10.0
+        self.params["contrast"] = 1.0
+        
+    def create(self):
+        """
+            Create an instance of the shape
+            @return: instance of the shape
+        """
+        self.shapeObject = geoshapespy.new_cylinder(\
+            self.params["radius"], self.params["length"])
+
+        ShapeDescriptor.create(self)
+        return self.shapeObject
+        
+
+class EllipsoidDescriptor(ShapeDescriptor):
+    """
+        Descriptor for an ellipsoid
+    """
+    def __init__(self):
+        """
+            Initialization
+        """ 
+        ShapeDescriptor.__init__(self)
+        # Default parameters
+        self.params["type"]   = "ellipsoid"
+        self.params["radius_x"] = 30.0
+        self.params["radius_y"] = 20.0
+        self.params["radius_z"] = 10.0
+        self.params["contrast"] = 1.0
+        
+    def create(self):
+        """
+            Create an instance of the shape
+            @return: instance of the shape
+        """
+        self.shapeObject = geoshapespy.new_ellipsoid(\
+            self.params["radius_x"], self.params["radius_y"], 
+            self.params["radius_z"])
+        
+        ShapeDescriptor.create(self)   
+        return self.shapeObject
+        
+class HelixDescriptor(ShapeDescriptor):
+    """
+        Descriptor for an helix
+    """
+    def __init__(self):
+        """
+            Initialization
+        """ 
+        ShapeDescriptor.__init__(self)
+        # Default parameters
+        self.params["type"]   = "singlehelix"
+        self.params["radius_helix"] = 10.0
+        self.params["radius_tube"] = 3.0
+        self.params["pitch"] = 34.0
+        self.params["turns"] = 3.0
+        self.params["contrast"] = 1.0
+
+    def create(self):
+        """
+            Create an instance of the shape
+            @return: instance of the shape
+        """
+        self.shapeObject = geoshapespy.new_singlehelix(\
+            self.params["radius_helix"], self.params["radius_tube"], 
+            self.params["pitch"], self.params["turns"])
+        
+        ShapeDescriptor.create(self)   
+        return self.shapeObject
+        
+class PDBDescriptor(ShapeDescriptor):
+    """
+        Descriptor for a PDB set of points
+    """
+    def __init__(self, filename):
+        """
+            Initialization
+            @param filename: name of the PDB file to load
+        """ 
+        ShapeDescriptor.__init__(self)
+        # Default parameters
+        self.params["type"]   = "pdb"
+        self.params["file"] = filename
+        self.params['is_lores'] = False
+
+    def create(self):
+        """
+            Create an instance of the shape
+            @return: instance of the shape
+        """
+        self.shapeObject = pointsmodelpy.new_pdbmodel()
+        pointsmodelpy.pdbmodel_add(self.shapeObject, self.params['file'])       
+        
+        #ShapeDescriptor.create(self)   
+        return self.shapeObject
+        
+# Define a dictionary for the shape until we find
+# a better way to create them
+shape_dict = {'sphere':SphereDescriptor,
+              'cylinder':CylinderDescriptor,
+              'ellipsoid':EllipsoidDescriptor,
+              'singlehelix':HelixDescriptor}
+        
+class VolumeCanvas(BaseComponent):
+    """
+        Class representing an empty space volume to add 
+        geometrical object to.
+    """
+    
+    def __init__(self):
+        """
+            Initialization
+        """
+        BaseComponent.__init__(self)
+        
+        ## Maximum value of q reachable
+        self.params['q_max'] = 0.1
+        self.params['lores_density'] = 0.1
+        self.params['scale'] = 1.0
+        self.params['background'] = 0.0
+        
+        self.lores_model = pointsmodelpy.new_loresmodel(self.params['lores_density'])
+        self.complex_model = pointsmodelpy.new_complexmodel()
+        self.shapes = {}
+        self.shapecount = 0        
+        self.points = None
+        self.npts = 0
+        self.hasPr = False        
+        
+    def add(self, shape, id = None):
+        """
+            @param shape: name of the object to add to the canvas [string]
+            @param id: string handle for the object [string] [optional]
+            @return: string handle for the object
+        """
+        # If the handle is not provided, create one
+        if id == None:
+            id = "shape"+str(self.shapecount)
+ 
+        #shapeDesc = ShapeDescriptor(shape.lower())
+        if shape.lower() in shape_dict:
+            shapeDesc = shape_dict[shape.lower()]()
+        elif os.path.isfile(shape):
+            # A valid filename was supplier, create a PDB object
+            shapeDesc = PDBDescriptor(shape)
+        else:
+            raise ValueError, "VolumeCanvas.add: Unknown shape %s" % shape
+        
+        # Self the order number
+        shapeDesc.params['order'] = self.shapecount
+        # Store the shape in a dictionary entry associated
+        # with the handle
+        self.shapes[id] = shapeDesc
+        self.shapecount += 1
+
+        #model changed, need to recalculate P(r)
+        self.hasPr = False
+
+        return id
+
+    def delete(self, id):
+        """
+            Delete a shape. The ID for the shape is required.
+            @param id: string handle for the object [string] [optional]
+        """
+
+        if self.shapes.has_key(id):
+            del self.shapes[id]
+        else:
+            raise KeyError, "VolumeCanvas.delete: could not find shape ID"
+
+        #model changed, need to recalculate P(r)
+        self.hasPr = False
+
+
+    def setParam(self, name, value):    
+        """
+            @param name: name of the parameter to change
+            @param value: value to give the parameter
+        """
+        
+        # Lowercase for case insensitivity
+        name = name.lower()
+        
+        # Look for shape access
+        toks = name.split('.')
+        
+        # If a shape identifier was given, look the shape up
+        # in the dictionary
+        if len(toks)>1:
+            if toks[0] in self.shapes.keys():
+                # The shape was found, now look for the parameter
+                if toks[1] in self.shapes[toks[0]].params:
+                    # The parameter was found, now change it
+                    self.shapes[toks[0]].params[toks[1]] = value
+                    self.hasPr = False
+                else:
+                    raise ValueError, "Could not find parameter %s" % name
+            else:
+                raise ValueError, "Could not find shape %s" % toks[0]
+        
+        else:
+            # If we are not accessing the parameters of a 
+            # shape, see if the parameter is part of this object
+            BaseComponent.setParam(self, name, value)
+            self.hasPr = False
+
+    def getParam(self, name):    
+        """
+            @param name: name of the parameter to change
+        """
+        #TODO: clean this up
+        
+        # Lowercase for case insensitivity
+        name = name.lower()
+        
+        # Look for sub-model access
+        toks = name.split('.')
+        if len(toks) == 1:
+            try:
+                self.params.has_key(toks[0])
+            except KeyError:
+                raise ValueError, \
+                    "VolumeCanvas.getParam: Could not find %s" % name
+
+            value = self.params[toks[0]]
+            if isinstance(value, ShapeDescriptor):
+                raise ValueError, \
+                    "VolumeCanvas.getParam: Cannot get parameter value." 
+            else:
+                return value
+
+        elif len(toks) == 2:
+            try:
+                self.shapes.has_key(toks[0])
+            except KeyError:
+                raise ValueError, \
+                    "VolumeCanvas.getParam: Could not find %s" % name
+
+            shapeinstance = self.shapes[toks[0]]
+
+            try:
+                shapeinstance.params.has_key(toks[1])
+            except KeyError:
+                raise ValueError, \
+                    "VolumeCanvas.getParam: Could not find %s" % name
+
+            return shapeinstance.params[toks[1]]
+
+        else:
+            raise ValueError, \
+                "VolumeCanvas.getParam: Could not find %s" % name
+            
+    def getParamList(self, shapeid = None):
+        """
+	       return a full list of all available parameters from 
+           self.params.keys(). If a key in self.params is a instance
+           of ShapeDescriptor, extend the return list to: 
+           [param1,param2,shapeid.param1,shapeid.param2.......]
+
+           If shapeid is provided, return the list of parameters that
+           belongs to that shape id only : [shapeid.param1, shapeid.param2...]
+        """
+
+        param_list = []
+        if shapeid == None:        
+            for key1 in self.params.keys():
+                #value1 = self.params[key1]
+                param_list.append(key1)
+            for key2 in self.shapes.keys():
+                value2 = self.shapes[key2]
+                header = key2 + '.'
+                for key3 in value2.params.keys():   
+                    fullname = header + key3                 
+                    param_list.append(fullname)
+     
+        else:
+            try:
+                self.shapes.has_key(shapeid)
+            except KeyError:
+                raise ValueError, \
+                    "VolumeCanvas: getParamList: Could not find %s" % shapeid
+            header = shapeid + '.'
+            param_list = self.shapes[shapeid].params.keys()  
+            for i in range(len(param_list)):
+                param_list[i] = header + param_list[i]
+
+        return param_list
+
+    def getShapeList(self):
+        """
+            Return a list of the shapes 
+        """
+        return self.shapes.keys()
+
+    def addSingleShape(self, shapeDesc):
+        """
+            create shapeobject based on shapeDesc
+            @param shapeDesc: shape description
+        """
+        #Create the object model
+        shapeDesc.create()
+                    
+        if shapeDesc.params['is_lores']:
+            # Add the shape to the lores_model
+            pointsmodelpy.lores_add(self.lores_model, 
+                shapeDesc.shapeObject, shapeDesc.params['contrast'])  
+
+    def createVolumeFromList(self):
+        """
+            Create a new lores model with all the shapes in our internal list
+            Whenever we change a parameter of a shape, we have to re-create
+            the whole thing.
+            
+            Items with higher 'order' number take precedence for regions
+            of space that are shared with other objects. Points in the 
+            overlapping region belonging to objects with lower 'order'
+            will be ignored.
+            
+            Items are added in decreasing 'order' number.
+            The item with the highest 'order' will be added *first*.
+            [That conventions is prescribed by the realSpaceModeling module]
+        """
+        
+        # Create empty model
+        self.lores_model = \
+            pointsmodelpy.new_loresmodel(self.params['lores_density'])
+
+        # Create empty complex model
+        self.complex_model = pointsmodelpy.new_complexmodel()
+       
+        # Order the object first
+        obj_list = []
+    
+        for shape in self.shapes:
+            order = self.shapes[shape].params['order']
+            # find where to place it in the list
+            stored = False
+            
+            for i in range(len(obj_list)):
+                if obj_list[i][0] > order:
+                    obj_list.insert(i, [order, shape])
+                    stored = True
+                    break
+            
+            if not stored:
+                obj_list.append([order, shape])
+                
+        # Add each shape
+        len_list = len(obj_list)
+        for i in range(len_list-1, -1, -1):
+            shapedesc = self.shapes[obj_list[i][1]]
+            self.addSingleShape(shapedesc)
+
+        return 0     
+    
+    def getPr(self):
+        """
+            Calculate P(r)
+            @return: calculation output flag 
+        """
+        # To find a complete example of the correct call order:
+        # In LORES2, in actionclass.py, method CalculateAction._get_iq()
+        
+        # If there are not shapes, do nothing
+        if len(self.shapes) == 0:
+            self.hasPr = False
+            return 0
+        
+        # generate space filling points from shape list
+        self.createVolumeFromList()
+
+        self.points = pointsmodelpy.new_point3dvec()
+
+        pointsmodelpy.complexmodel_add(self.complex_model, 
+                                        self.lores_model, "LORES")
+        for shape in self.shapes:
+            if self.shapes[shape].params['is_lores'] == False:
+                pointsmodelpy.complexmodel_add(self.complex_model, 
+                    self.shapes[shape].shapeObject, "PDB")
+        
+        #pointsmodelpy.get_lorespoints(self.lores_model, self.points)
+        self.npts = pointsmodelpy.get_complexpoints(self.complex_model, self.points)
+        
+        # expecting the rmax is a positive float or 0. The maximum distance.
+        #rmax = pointsmodelpy.get_lores_pr(self.lores_model, self.points)   
+         
+        rmax = pointsmodelpy.get_complex_pr(self.complex_model, self.points) 
+        self.hasPr = True   
+
+        return rmax
+        
+    def run(self, q = 0):
+        """
+            Returns the value of I(q) for a given q-value
+            @param q: q-value [float]
+            @return: I(q) [float]
+        """
+        #TODO: The right simulation function should be
+        # called according to the type of input we get.
+        # For now, only a q length is supported.
+        return getIq(q)
+        
+    def getIq(self, q):
+        """
+            Returns the value of I(q) for a given q-value
+            
+            This method should remain internal to the class
+            and the run() method should be used instead.
+            
+            @param q: q-value [float]
+            @return: I(q) [float]
+        """
+        
+        if self.hasPr == False:
+            self.getPr()
+
+        # By dividing by the density instead of the actuall V/N, 
+        # we have an uncertainty of +-1 on N because the number 
+        # of points chosen for the simulation is int(density*volume).
+        # Propagation of error gives:
+        #   delta(1/density^2) = 2*(1/density^2)/N
+        # where N is stored in self.npts
+
+        norm =  1.0e8/self.params['lores_density']*self.params['scale']
+        #return norm*pointsmodelpy.get_lores_i(self.lores_model, q)
+        return norm*pointsmodelpy.get_complex_i(self.complex_model, q)\
+            + self.params['background']
+    
+    def getError(self, q):
+        """
+            Returns the error of I(q) for a given q-value
+            @param q: q-value [float]
+            @return: I(q) [float]
+        """
+        
+        if self.hasPr == False:
+            self.getPr()
+
+        # By dividing by the density instead of the actuall V/N, 
+        # we have an uncertainty of +-1 on N because the number 
+        # of points chosen for the simulation is int(density*volume).
+        # Propagation of error gives:
+        #   delta(1/density^2) = 2*(1/density^2)/N
+        # where N is stored in self.npts
+
+        norm =  1.0e8/self.params['lores_density']*self.params['scale']
+        #return norm*pointsmodelpy.get_lores_i(self.lores_model, q)
+        return norm*pointsmodelpy.get_complex_i_error(self.complex_model, q)\
+            + self.params['background']
+    
+    def getIqError(self, q):
+        """
+            Return the simulated value along with its estimated
+            error for a given q-value
+            
+            Propagation of errors is used to evaluate the
+            uncertainty.
+            
+            @param q: q-value [float]
+            @return: mean, error [float, float]
+        """
+        val = self.getIq(q)
+        # Simulation error (statistical)
+        err = self.getError(q)
+        # Error on V/N
+        simerr = 2*val/self.npts
+        return val, err+simerr
+        
Index: sansrealspace/src/realspace/__init__.py
===================================================================
--- sansrealspace/src/realspace/__init__.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/__init__.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,81 @@
+"""
+    Real-Space Modeling for SANS
+"""
+## \mainpage Real-Space Modeling for SANS
+#
+# \section intro_sec Introduction
+# This module provides SANS scattering intensity simulation 
+# based on real-space modeling.
+#
+# Documentation can be found here: 
+#    http://danse.us/trac/sans/wiki/RealSpaceModeling
+#
+# \section install_sec Installation
+#
+# \subsection obtain Obtaining the Code
+#
+# The code is available here:
+# \verbatim
+#$ svn co svn://danse.us/sans/realSpaceModeling
+#$ svn co svn://danse.us/sans/RealSpaceTopLayer
+# \endverbatim
+#
+# \subsection depends External Dependencies
+# You have to have the DANSE distutils-adpt package installed:
+# \verbatime
+#$ svn co svn://danse.us/distutils-adpt
+# \endverbatim
+#
+# \subsection build Building the code
+# The standard python package can be built with distutils.
+# From the realSpaceModeling directory:
+# \verbatim
+#$ python setup.py install
+# \endverbatim
+#
+# From the RealSpaceTopLayer/src directory:
+# \verbatim
+#$ python setup.py install
+# \endverbatim
+#
+# \section overview_sec Package Overview
+# 
+# \subsection class Class Diagram:
+# \image html real-space-class-diagram.png
+#
+# \subsection behav Behavior Enumeration:
+# \image html enum.gif
+#
+# \subsection Tutorial
+# To create an empty canvas:
+# \verbatim
+#import sans.realspace.VolumeCanvas as VolumeCanvas
+#    canvas = VolumeCanvas.VolumeCanvas()
+# \endverbatim
+#
+# To set the simulation point density:
+# \verbatim
+#    canvas.setParam('lores_density', 0.01)
+# \endverbatim
+#
+# To add an object:
+# \verbatim
+#    handle = canvas.add('sphere')
+#    canvas.setParam('%s.radius' % handle, 15.0)
+# \endverbatim
+#
+# To evaluate the scattering intensity at a given q:
+# \verbatim
+#    output, error = canvas.getIqError(q=0.1)
+# \endverbatim
+#
+# To get the value of a parameter:
+# \verbatim
+#    canvas.getParam('scale')
+# \endverbatim
+#
+# Examples are available as unit tests under sans.realspace.test.
+#
+# \section help_sec Contact Info
+# Code and Documentation by Jing Zhou as part of the DANSE project.
+
Index: sansrealspace/src/realspace/test/coreshell_d=0.1_Iq.txt
===================================================================
--- sansrealspace/src/realspace/test/coreshell_d=0.1_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/coreshell_d=0.1_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,52 @@
+PARS: {'core_sld': 1.0, 'thickness': 5, 'solvent_sld': 0.0, 'scale': 1.0, 'radius': 15, 'background': 0.0, 'shell_sld': -0.7297297297297296}
+<q>  <ana>  <sim>  <err>
+0.001  546.461  -7.27421e+008  1.37292e+009
+0.0213878  1.12037e+008  -6.32851e+008  1.34853e+009
+0.0417755  1.5392e+009  7.67197e+008  1.28774e+009
+0.0621633  6.85689e+009  6.09968e+009  1.20977e+009
+0.082551  1.86312e+010  1.7983e+010  1.13673e+009
+0.102939  3.77797e+010  3.73566e+010  1.08398e+009
+0.123327  6.25086e+010  6.23996e+010  1.05461e+009
+0.143714  8.83525e+010  8.85733e+010  1.0418e+009
+0.164102  1.09375e+011  1.09848e+011  1.03577e+009
+0.18449  1.20102e+011  1.20672e+011  1.02889e+009
+0.204878  1.17429e+011  1.17916e+011  1.01729e+009
+0.225265  1.01772e+011  1.02032e+011  1.00024e+009
+0.245653  7.69907e+010  7.69709e+010  9.79426e+008
+0.266041  4.91381e+010  4.88967e+010  9.58013e+008
+0.286429  2.4505e+010  2.41902e+010  9.39492e+008
+0.306816  7.70105e+009  7.49699e+009  9.26413e+008
+0.327204  4.46446e+008  5.06758e+008  9.1963e+008
+0.347592  1.44323e+009  1.83859e+009  9.18199e+008
+0.36798  7.26659e+009  7.96467e+009  9.19939e+008
+0.388367  1.38447e+010  1.47281e+010  9.22303e+008
+0.408755  1.79268e+010  1.88388e+010  9.23283e+008
+0.429143  1.80167e+010  1.88151e+010  9.21956e+008
+0.449531  1.45166e+010  1.51141e+010  9.18572e+008
+0.469918  9.15928e+009  9.5374e+009  9.14181e+008
+0.490306  4.06238e+009  4.25686e+009  9.10058e+008
+0.510694  8.28382e+008  8.98016e+008  9.0716e+008
+0.531082  2.58879e+007  2.04037e+007  9.05812e+008
+0.551469  1.17967e+009  1.12139e+009  9.05703e+008
+0.571857  3.17457e+009  3.05995e+009  9.06142e+008
+0.592245  4.83142e+009  4.64842e+009  9.06414e+008
+0.612633  5.38985e+009  5.13989e+009  9.06068e+008
+0.63302  4.72163e+009  4.43349e+009  9.05041e+008
+0.653408  3.24255e+009  2.97061e+009  9.0359e+008
+0.673796  1.62119e+009  1.42853e+009  9.02118e+008
+0.694184  4.47037e+008  3.81628e+008  9.00963e+008
+0.714571  4.15178e+006  7.92793e+007  9.00274e+008
+0.734959  2.21749e+008  4.09993e+008  8.9998e+008
+0.755347  7.83703e+008  1.02708e+009  8.99873e+008
+0.775735  1.31559e+009  1.54726e+009  8.99722e+008
+0.796122  1.55288e+009  1.72101e+009  8.99377e+008
+0.81651  1.42344e+009  1.50737e+009  8.98811e+008
+0.836898  1.02948e+009  1.0424e+009  8.98107e+008
+0.857286  5.60678e+008  5.39156e+008  8.97389e+008
+0.877673  1.92343e+008  1.78489e+008  8.96765e+008
+0.898061  1.59082e+007  4.03444e+007  8.9628e+008
+0.918449  2.32131e+007  9.55129e+007  8.95919e+008
+0.938837  1.3713e+008  2.4636e+008  8.95622e+008
+0.959224  2.62821e+008  3.86108e+008  8.95324e+008
+0.979612  3.31767e+008  4.45768e+008  8.94984e+008
+1  3.21634e+008  4.11445e+008  8.94592e+008
Index: sansrealspace/src/realspace/test/cylinder_d=0.1_Iq.txt
===================================================================
--- sansrealspace/src/realspace/test/cylinder_d=0.1_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/cylinder_d=0.1_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,27 @@
+PARS: {'scale': 1.0, 'length': 100, 'radius': 20, 'background': 0.0, 'cyl_theta': 1.0, 'contrast': 1.0, 'cyl_phi': 1.0}
+<q>  <ana>  <sim>  <err>
+0.001  1.2562e+013  1.25607e+013  3.67864e+009
+0.0217917  1.06945e+013  1.0691e+013  3.21973e+009
+0.0425833  6.98848e+012  6.98558e+012  2.39648e+009
+0.063375  3.89546e+012  3.89885e+012  1.75747e+009
+0.0841667  2.15182e+012  2.15808e+012  1.39268e+009
+0.104958  1.16773e+012  1.17248e+012  1.18625e+009
+0.12575  5.3522e+011  5.41059e+011  1.05731e+009
+0.146542  2.02193e+011  2.09984e+011  9.86651e+008
+0.167333  6.56338e+010  7.18402e+010  9.52195e+008
+0.188125  1.38312e+010  1.79966e+010  9.34606e+008
+0.208917  1.1972e+010  1.57779e+010  9.27539e+008
+0.229708  2.57696e+010  2.86744e+010  9.24382e+008
+0.2505  2.81981e+010  2.99685e+010  9.20381e+008
+0.271292  2.43503e+010  2.60824e+010  9.16284e+008
+0.292083  1.74679e+010  1.9053e+010  9.12237e+008
+0.312875  7.68598e+009  8.71654e+009  9.08067e+008
+0.333667  2.24612e+009  3.3301e+009  9.04986e+008
+0.354458  1.50351e+009  2.74945e+009  9.02909e+008
+0.37525  1.76444e+009  2.73377e+009  9.01113e+008
+0.396042  3.30229e+009  4.21174e+009  8.99704e+008
+0.416833  4.45731e+009  5.54871e+009  8.98392e+008
+0.437625  3.52632e+009  4.50585e+009  8.96801e+008
+0.458417  2.26254e+009  3.02617e+009  8.95224e+008
+0.479208  1.229e+009  1.96096e+009  8.93779e+008
+0.5  2.3872e+008  9.34726e+008  8.92399e+008
Index: sansrealspace/src/realspace/test/early_test.py
===================================================================
--- sansrealspace/src/realspace/test/early_test.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/early_test.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,174 @@
+
+import VolumeCanvas
+from sans.models.SphereModel import SphereModel
+from sans.models.CoreShellModel import CoreShellModel
+
+import math, time
+    
+def form_factor(q, r):
+    qr = q*r
+    f = 3*( math.sin(qr) - qr*math.cos(qr) ) / (qr*qr*qr)
+    return f*f
+    
+def test_1():
+    
+    radius = 15
+    
+    density = .1
+    vol = 4/3*math.pi*radius*radius*radius
+    npts = vol*density
+     
+    canvas = VolumeCanvas.VolumeCanvas()
+    canvas.setParam('lores_density', density)
+    handle = canvas.add('sphere')
+    canvas.setParam('%s.radius' % handle, radius)
+    canvas.setParam('%s.contrast' % handle, 1.0)
+    
+    
+    if False:
+        # Time test
+        t_0 = time.time()
+        value_1 = 1.0e8*canvas.getIq(0.1)
+        print "density = 0.1:  output=%g  time=%g" % (value_1, time.time()-t_0)
+        
+        t_0 = time.time()
+        canvas.setParam('lores_density', 1)
+        value_1 = 1.0e8*canvas.getIq(0.1)
+        print "density = 1000:  output=%g  time=%g" % (value_1, time.time()-t_0)
+        
+        t_0 = time.time()
+        canvas.setParam('lores_density', 0.01)
+        value_1 = 1.0e8*canvas.getIq(0.1)
+        print "density = 0.00001:  output=%g  time=%g" % (value_1, time.time()-t_0)
+        print
+    
+    
+    sphere = SphereModel()
+    sphere.setParam('radius', radius)
+    sphere.setParam('scale', 1.0)
+    sphere.setParam('contrast', 1.0)
+        
+        
+    # Simple sphere sum(Pr) = (rho*V)^2    
+    # each p(r) point has a volume of 1/density    
+        
+    for i in range(35):
+        q = 0.001 + 0.01*i
+        
+        
+        
+        #sim_1 = 1.0e8*canvas.getIq(q)*4/3*math.pi/(density*density*density)
+        sim_1 = canvas.getIq(q)
+        ana_1 = sphere.run(q)
+        #ana_1 = form_factor(q, radius)
+        
+        print "q=%g  sim=%g  ana=%g   ratio=%g" % (q, sim_1, ana_1, sim_1/ana_1)
+    
+def test_2():
+    radius = 15.0
+    thickness = 5.0
+    
+    core_vol = 4.0/3.0*math.pi*radius*radius*radius
+    outer_radius = radius+thickness
+    shell_vol = 4.0/3.0*math.pi*outer_radius*outer_radius*outer_radius - core_vol
+    shell_sld = -1.0*core_vol/shell_vol
+    print "Shell SLD", shell_sld
+
+    
+    density = .1
+    vol = 4/3*math.pi*radius*radius*radius
+    npts = vol*density
+     
+    canvas = VolumeCanvas.VolumeCanvas()
+    canvas.setParam('lores_density', density)
+    handle = canvas.add('sphere')
+    canvas.setParam('%s.radius' % handle, outer_radius)
+    canvas.setParam('%s.contrast' % handle, shell_sld)
+   
+    handle2 = canvas.add('sphere')
+    canvas.setParam('%s.radius' % handle2, radius)
+    canvas.setParam('%s.contrast' % handle2, 1.0)
+   
+   
+   
+    # Core-shell
+    sphere = CoreShellModel()
+    # Core radius
+    sphere.setParam('radius', radius)
+    # Shell thickness
+    sphere.setParam('thickness', thickness)
+    sphere.setParam('core_sld', 1.0)
+    
+    
+    sphere.setParam('shell_sld', shell_sld)
+    sphere.setParam('solvent_sld',0.0)
+    sphere.setParam('background',0.0)
+    sphere.setParam('scale',1.0)
+   
+    out = open("lores_test.txt",'w')
+    out.write("<q> <sim> <ana>\n")
+    
+    for i in range(65):
+        q = 0.001 + 0.01*i
+        
+        # For each volume integral that we change to a sum,
+        # we must multiply by 1/density = V/N
+        # Since we want P(r)/V, we will need to multiply
+        # the sum by 1/(N*density), where N is the number of
+        # points without overlap. Since we already divide
+        # by N when calculating I(q), we only need to divide
+        # by the density here. We divide by N in the 
+        # calculation because it is difficult to estimate it here.
+        
+        
+        # Put the factor 2 in the simulation two...
+        sim_1 = canvas.getIq(q)
+        ana_1 = sphere.run(q)
+        
+        print "q=%g  sim=%g  ana=%g   ratio=%g" % (q, sim_1, ana_1, sim_1/ana_1)
+        out.write( "%g  %g  %g\n" % (q, sim_1, ana_1))
+
+    out.close()
+
+def test_4():
+    radius = 15
+    
+    density = .1
+    vol = 4/3*math.pi*radius*radius*radius
+    npts = vol*density
+
+    
+    canvas = VolumeCanvas.VolumeCanvas()
+    canvas.setParam('lores_density', density)
+    #handle = canvas.add('sphere')
+    #canvas.setParam('%s.radius' % handle, radius)
+    #canvas.setParam('%s.contrast' % handle, 1.0)
+    
+    pdb = canvas.add('test.pdb')
+    
+    
+    
+    sphere = SphereModel()
+    sphere.setParam('radius', radius)
+    sphere.setParam('scale', 1.0)
+    sphere.setParam('contrast', 1.0)
+        
+        
+    # Simple sphere sum(Pr) = (rho*V)^2    
+    # each p(r) point has a volume of 1/density    
+        
+    for i in range(35):
+        q = 0.001 + 0.01*i
+        
+        
+        
+        #sim_1 = 1.0e8*canvas.getIq(q)*4/3*math.pi/(density*density*density)
+        sim_1 = canvas.getIq(q)
+        ana_1 = sphere.run(q)
+        #ana_1 = form_factor(q, radius)
+        
+        print "q=%g  sim=%g  ana=%g   ratio=%g" % (q, sim_1, ana_1, sim_1/ana_1)
+
+    
+if __name__ == "__main__":
+    test_1()
Index: sansrealspace/src/realspace/test/ellipsoid_d=0.05_Iq.txt
===================================================================
--- sansrealspace/src/realspace/test/ellipsoid_d=0.05_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/ellipsoid_d=0.05_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,27 @@
+PARS: {'scale': 1.0, 'axis_theta': 1.5700000000000001, 'radius_b': 15, 'radius_a': 20, 'axis_phi': 0.0, 'background': 0.0, 'contrast': 1.0}
+<q>  <ana>  <sim>  <err>
+0.001  1.88485e+012  1.88189e+012  7.35402e+009
+0.0217917  1.83483e+012  1.83147e+012  7.18365e+009
+0.0425833  1.69985e+012  1.69545e+012  6.73107e+009
+0.063375  1.49672e+012  1.49089e+012  6.07014e+009
+0.0841667  1.24958e+012  1.24229e+012  5.30092e+009
+0.104958  9.8564e+011  9.77186e+011  4.52402e+009
+0.12575  7.30614e+011  7.21587e+011  3.81724e+009
+0.146542  5.05003e+011  4.96139e+011  3.22446e+009
+0.167333  3.21752e+011  3.13778e+011  2.76003e+009
+0.188125  1.85658e+011  1.79144e+011  2.41966e+009
+0.208917  9.43704e+010  8.96235e+010  2.18869e+009
+0.229708  4.04806e+010  3.75119e+010  2.04629e+009
+0.2505  1.40738e+010  1.2626e+010  1.96861e+009
+0.271292  5.08948e+009  4.7208e+009  1.93221e+009
+0.292083  5.04776e+009  5.24445e+009  1.91756e+009
+0.312875  7.9394e+009  8.22924e+009  1.91088e+009
+0.333667  1.033e+010  1.0371e+010  1.90448e+009
+0.354458  1.09014e+010  1.05292e+010  1.89551e+009
+0.37525  9.72349e+009  8.95211e+009  1.88421e+009
+0.396042  7.52237e+009  6.50489e+009  1.87219e+009
+0.416833  5.12122e+009  4.08427e+009  1.86111e+009
+0.437625  3.12049e+009  2.29207e+009  1.85209e+009
+0.458417  1.79654e+009  1.34508e+009  1.84545e+009
+0.479208  1.14661e+009  1.14435e+009  1.84089e+009
+0.5  9.98764e+008  1.41508e+009  1.83776e+009
Index: sansrealspace/src/realspace/test/helix_d=0.05_Iq.txt
===================================================================
--- sansrealspace/src/realspace/test/helix_d=0.05_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/helix_d=0.05_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,27 @@
+PARS: {'q_max': 0.10000000000000001, 'lores_density': 0.050000000000000003, 'background': 0.0, 'scale': 1.0}
+<q>  <ana>  <sim>  <err>
+0.001  1  3.3589e+011  7.32709e+009
+0.0217917  1  2.88542e+011  6.46473e+009
+0.0425833  1  1.95952e+011  4.95096e+009
+0.063375  1  1.21406e+011  3.811e+009
+0.0841667  1  8.12728e+010  3.1642e+009
+0.104958  1  5.70219e+010  2.77326e+009
+0.12575  1  3.7538e+010  2.49052e+009
+0.146542  1  2.3773e+010  2.29431e+009
+0.167333  1  1.61515e+010  2.1751e+009
+0.188125  1  1.39993e+010  2.12225e+009
+0.208917  1  1.62402e+010  2.12553e+009
+0.229708  1  1.93111e+010  2.14395e+009
+0.2505  1  2.01724e+010  2.1398e+009
+0.271292  1  1.90299e+010  2.11382e+009
+0.292083  1  1.69669e+010  2.07787e+009
+0.312875  1  1.4305e+010  2.03583e+009
+0.333667  1  1.13903e+010  1.99203e+009
+0.354458  1  8.76787e+009  1.95283e+009
+0.37525  1  6.91759e+009  1.9236e+009
+0.396042  1  6.09987e+009  1.90724e+009
+0.416833  1  6.01727e+009  1.90011e+009
+0.437625  1  6.09422e+009  1.89539e+009
+0.458417  1  6.07945e+009  1.89004e+009
+0.479208  1  5.93162e+009  1.8835e+009
+0.5  1  5.57243e+009  1.87481e+009
Index: sansrealspace/src/realspace/test/sim_validation.py
===================================================================
--- sansrealspace/src/realspace/test/sim_validation.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/sim_validation.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,274 @@
+"""
+    Validation tests for real-space simulation of I(q)
+    
+    @copyright: University of Tennessee, 2007
+    @license: This software is provided as part of the DANSE project
+"""
+import math, time, pylab
+
+try:
+    import VolumeCanvas
+    print "Testing local version"
+except:
+    print "Testing installed version"
+    import sans.realspace.VolumeCanvas as VolumeCanvas
+     
+class Validator:
+ 
+    def __init__(self):
+        self.density = 0.1
+        self.create()
+        
+    def create(self):
+        pass
+         
+    def run_sim(self, q, density=None):
+        """
+            Calculate the mean and error of the simulation
+            @param q: q-value to calculate at
+            @param density: point density of simulation
+            #return: mean, error
+        """
+        if not density == None:
+            self.density = density
+            self.create()
+        
+        return self.canvas.getIqError(q)
+         
+    def run_ana(self, q):
+        """
+            Return analytical value
+            @param q: q-value to evaluate at [float]
+            @return: analytical output [float]
+        """
+        return self.ana.run(q)      
+    
+class SphereValidator(Validator):
+    
+    def __init__(self, radius=15, density = 0.01):
+        from sans.models.SphereModel import SphereModel
+        
+        self.name = 'sphere'
+        self.radius = radius
+        self.density = density
+        
+        self.ana = SphereModel()
+        self.ana.setParam('scale', 1.0)
+        self.ana.setParam('contrast', 1.0)
+        self.ana.setParam('background', 0.0)
+        self.ana.setParam('radius', radius)
+        self.create()
+       
+    def create(self):
+        canvas = VolumeCanvas.VolumeCanvas()
+        canvas.setParam('lores_density', self.density)
+        handle = canvas.add('sphere')
+        canvas.setParam('%s.radius' % handle, self.radius)
+        canvas.setParam('scale' , 1.0)
+        canvas.setParam('%s.contrast' % handle, 1.0)
+        canvas.setParam('background' , 0.0)
+        self.canvas = canvas   
+           
+class CylinderValidator(Validator):
+    
+    def __init__(self, radius=15, length=100, density = 0.01):
+        from sans.models.CylinderModel import CylinderModel
+        
+        self.name = 'cylinder'
+        self.radius = radius
+        self.length = length
+        self.density = density
+        
+        self.ana = CylinderModel()
+        self.ana.setParam('scale', 1.0)
+        self.ana.setParam('contrast', 1.0)
+        self.ana.setParam('background', 0.0)
+        self.ana.setParam('radius', radius)
+        self.ana.setParam('length', length)
+        self.create()
+       
+    def create(self):
+        canvas = VolumeCanvas.VolumeCanvas()
+        canvas.setParam('lores_density', self.density)
+        handle = canvas.add('cylinder')
+        canvas.setParam('%s.radius' % handle, self.radius)
+        canvas.setParam('%s.length' % handle, self.length)
+        canvas.setParam('scale' , 1.0)
+        canvas.setParam('%s.contrast' % handle, 1.0)
+        canvas.setParam('background' , 0.0)
+        self.canvas = canvas   
+           
+class EllipsoidValidator(Validator):
+    
+    def __init__(self, radius_a=60, radius_b=10, density = 0.01):
+        from sans.models.EllipsoidModel import EllipsoidModel
+        #from sans.models.SphereModel import SphereModel
+        
+        self.name = 'ellipsoid'
+        self.radius_a = radius_a
+        self.radius_b = radius_b
+        self.density = density
+        
+        self.ana = EllipsoidModel()
+        #self.ana = SphereModel()
+        self.ana.setParam('scale', 1.0)
+        self.ana.setParam('contrast', 1.0)
+        self.ana.setParam('background', 0.0)
+        self.ana.setParam('radius_a', radius_a)
+        self.ana.setParam('radius_b', radius_b)
+        #self.ana.setParam('radius', radius_a)
+
+        self.create()
+       
+    def create(self):
+        canvas = VolumeCanvas.VolumeCanvas()
+        canvas.setParam('lores_density', self.density)
+        handle = canvas.add('ellipsoid')
+        canvas.setParam('%s.radius_x' % handle, self.radius_a)
+        canvas.setParam('%s.radius_y' % handle, self.radius_b)
+        canvas.setParam('%s.radius_z' % handle, self.radius_b)
+        canvas.setParam('scale' , 1.0)
+        canvas.setParam('%s.contrast' % handle, 1.0)
+        canvas.setParam('background' , 0.0)
+        self.canvas = canvas
+           
+class HelixValidator(Validator):
+    
+    def __init__(self, density = 0.01):
+        self.name = 'helix'
+        self.density = density
+        self.create()
+       
+    def create(self):
+        canvas = VolumeCanvas.VolumeCanvas()
+        canvas.setParam('lores_density', self.density)
+        handle = canvas.add('singlehelix')
+        canvas.setParam('scale' , 1.0)
+        canvas.setParam('%s.contrast' % handle, 1.0)
+        canvas.setParam('background' , 0.0)
+        self.canvas = canvas
+        # just to write the parameters to the output file
+        self.ana = canvas
+        
+    def run_ana(self, q):
+        return 1
+
+           
+class CoreShellValidator(Validator):
+    
+    def __init__(self, radius=15, thickness=5, density = 0.01):
+        from sans.models.CoreShellModel import CoreShellModel
+        
+        self.name = 'coreshell'
+        self.radius = radius
+        
+        core_vol = 4.0/3.0*math.pi*radius*radius*radius
+        self.outer_radius = radius+thickness
+        shell_vol = 4.0/3.0*math.pi*self.outer_radius*self.outer_radius*self.outer_radius - core_vol
+        self.shell_sld = -1.0*core_vol/shell_vol
+
+        self.density = density
+           
+        # Core-shell
+        sphere = CoreShellModel()
+        # Core radius
+        sphere.setParam('radius', self.radius)
+        # Shell thickness
+        sphere.setParam('thickness', thickness)
+        sphere.setParam('core_sld', 1.0)
+        sphere.setParam('shell_sld', self.shell_sld)
+        sphere.setParam('solvent_sld', 0.0)
+        sphere.setParam('background', 0.0)
+        sphere.setParam('scale', 1.0)
+        self.ana = sphere
+        self.create()
+       
+    def create(self):
+        canvas = VolumeCanvas.VolumeCanvas()        
+        canvas.setParam('lores_density', self.density)
+        
+        handle = canvas.add('sphere')
+        canvas.setParam('%s.radius' % handle, self.outer_radius)
+        canvas.setParam('%s.contrast' % handle, self.shell_sld)
+       
+        handle2 = canvas.add('sphere')
+        canvas.setParam('%s.radius' % handle2, self.radius)
+        canvas.setParam('%s.contrast' % handle2, 1.0)
+               
+        canvas.setParam('scale' , 1.0)
+        canvas.setParam('background' , 0.0)
+        self.canvas = canvas 
+           
+def validate_model(validator, q_min, q_max, n_q):
+    """
+         Validate a model
+         An output file containing a comparison between
+         simulation and the analytical solution will be
+         produced.
+         
+         @param validator: validator object
+         @param q_min: minimum q
+         @param q_max: maximum q
+         @param n_q: number of q points
+         @param N: number of times to evaluate each simulation point
+    """
+    
+    q_list = pylab.arange(q_min, q_max*1.0001, (q_max-q_min)/(n_q-1))
+    
+    output = open('%s_d=%g_Iq.txt' % (validator.name, validator.density), 'w')
+    output.write("PARS: %s\n" % validator.ana.params)
+    output.write("<q>  <ana>  <sim>  <err>\n")
+    for q in q_list:
+        ana = validator.run_ana(q)
+        sim, err = validator.run_sim(q)
+        print "q=%-g  ana=%-g  sim=%-g  err=%-g  diff=%-g (%-g) %s" % (q, ana, sim, err, 
+                        (sim-ana), sim/ana, str(math.fabs(sim-ana)>err))
+        output.write("%g  %g  %g  %g\n" % (q, ana, sim, err))
+    output.close()
+       
+def check_density(validator, q, d_min, d_max, n_d): 
+    """
+       Check simulation output as a function of the density
+       An output file containing a comparison between
+       simulation and the analytical solution will be
+       produced.
+       
+       @param validator: validator object
+       @param q: q-value to evaluate at
+       @param d_min: minimum density
+       @param d_max: maximum density
+       @param n_d: number of density points
+       @param N: number of times to evaluate each simulation point
+    """
+    d_list = pylab.arange(d_min, d_max*1.0001, (d_max-d_min)/(n_d-1.0))
+    
+    output = open('%s_%g_density.txt' % (validator.name, q), 'w')
+    output.write("PARS: %s\n" % validator.ana.params)
+    output.write("<density>  <ana_d>  <sim_d>  <err_d>\n")
+    ana = validator.run_ana(q)
+    for d in d_list:
+        sim, err = validator.run_sim(q, density=d)
+        print "d=%-g  ana=%-g  sim=%-g  err=%-g  diff=%-g (%g) %s" % \
+            (d, ana, sim, err, (sim-ana), (sim-ana)/ana, 
+             str(math.fabs(sim-ana)>err))
+        output.write("%g  %g  %g  %g \n" % (d, ana, sim, err))
+    output.close()
+
+    
+if __name__ == '__main__':
+    #vali = CoreShellValidator(radius = 15, thickness=5, density = 0.01)
+    #validate_model(vali, q_min=0.001, q_max=1, n_q=50, N=40)
+
+    #vali = SphereValidator(radius = 20, density = 0.02)
+    #validate_model(vali, q_min=0.001, q_max=0.5, n_q=25, N=20)
+    
+    vali = CylinderValidator(radius = 20, length=100, density = 0.1)
+    validate_model(vali, q_min=0.001, q_max=0.5, n_q=25)
+    
+    #vali = EllipsoidValidator(radius_a = 20, radius_b=15, density = 0.05)
+    #validate_model(vali, q_min=0.001, q_max=0.5, n_q=25)
+    
+    #vali = HelixValidator(density = 0.05)
+    #validate_model(vali, q_min=0.001, q_max=0.5, n_q=25)
+    
+    
Index: sansrealspace/src/realspace/test/simulation_stimuli.py
===================================================================
--- sansrealspace/src/realspace/test/simulation_stimuli.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/simulation_stimuli.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,321 @@
+"""Automated testing for simulation
+
+    To be compatible with testcase_generator, a simulus
+    class must have:
+        setup(self):  to initialize the test-case
+        getRandomStimulus(self): to get a random stimulus name
+        stimuli that inherit from the Stimulus class
+        
+    @copyright: University of Tennessee, 2007
+    @license: This software is provided as part of the DANSE project.
+
+"""
+
+import testcase_generator as generator
+from testcase_generator import Stimulus
+try:
+    import VolumeCanvas
+    print "Testing local VolumeCanvas\n"
+except:
+    import sans.realspace.VolumeCanvas as VolumeCanvas
+
+def randomModel():
+    """ Return a random model name """
+    from random import random
+    from math import floor
+    
+    model_list = ['sphere', 'cylinder', 'ellipsoid', 'singlehelix']
+    rnd_id = int(floor(random()*len(model_list)))
+    
+    return model_list[rnd_id]
+
+
+class Simulation:
+    """Simulation testing class"""
+    
+    # Probability of stopping a test case without
+    # adding additional stimuli
+    end_frequency = 0.1
+    
+    def __init__(self):
+        self.end_flag = False
+        
+    def reset(self):
+        self.end_flag = False
+    
+    def setup(self):
+        """Setup the canvas for stimuli application"""
+        return VolumeCanvas.VolumeCanvas()
+    
+    def getRandomStimulus(self):
+        """Return the name of a random stimulus"""
+        
+        from random import random
+        
+        # If the end flag is up, compute I(q) one last time
+        if self.end_flag == True:
+            return None
+        
+        # Get the list of stimuli
+        attrs = dir(self)
+        stim_list = []
+        
+        # Get the list of simuli and 
+        # compute the total normalization
+        sum = 0
+        for item in attrs:
+            if item.endswith('Stimulus'):
+                obj = getattr(self, item)
+                if hasattr(obj, 'frequency'):
+                    stim_list.append(item)
+                    sum += getattr(self, item).frequency
+        
+        # Choose a stimulus
+        rnd = random()
+        
+        # Check if we need to stop
+        if rnd < self.end_frequency:
+            self.end_flag = True
+            return "GetIq"
+        
+        run_sum = 0
+        for item in stim_list:
+            run_sum += getattr(self, item).frequency/sum 
+            if run_sum >= rnd:
+                pos = item.index('Stimulus')
+                
+                return item[0:pos]
+        
+    class AddStimulus(Stimulus):
+        """Add an object to the canvas"""
+
+        frequency = 1.0
+        
+        def __call__(self, canvas):
+            """Apply stimulus"""
+            report = generator.StimulusReport(tag=self.name)
+        
+            # Select random model
+            add_model = randomModel()
+
+            # Add the model
+            handle = canvas.add(add_model)
+            
+            # Check that it is in the list of objects
+            if handle in canvas.getShapeList():
+                report.passed = 1
+            else:
+                report.log = "Add: tried to add %s" % add_model
+            
+            report.trace = "Added %s" % add_model
+            
+            return canvas, report
+    
+    class GetParamStimulus(Stimulus):
+        """Get the value of a canvas or shape parameter"""
+
+        frequency = 1.0
+        
+        def __call__(self, canvas):
+            """Apply stimulus"""
+            from random import random
+            import math
+            
+            report = generator.StimulusReport(tag=self.name)
+        
+            # Read a parameter
+            #TODO: access shape parameters
+            rnd = random()
+            i_rnd = int(math.floor(rnd*len(canvas.params)))
+            par_name = canvas.params.keys()[i_rnd]
+            value = canvas.getParam(par_name)
+            
+            # Check that it is in the list of objects
+            try:
+                float(value)
+                report.passed = 1
+            except:
+                report.log = "get: bad value for [%s]" % par_name
+            
+            report.trace = "Read %s" % par_name
+            
+            return canvas, report
+    
+    class GetIqStimulus(Stimulus):
+        """Calculate I(q)"""
+
+        frequency = 1.0
+        
+        def __call__(self, canvas):
+            report = generator.StimulusReport(tag=self.name)
+            
+            # Check that a float is returned
+            # Validation testing will be done elsewhere
+            value = canvas.getIq(0.1)
+            
+            # Check that it is in the list of objects
+            try:
+                float(value)
+                report.passed = 1
+            except:
+                report.log = "GetIq: bad value for Iq "+str(value)
+            
+            report.trace = "I(q) = %g" % value
+            return canvas, report
+    
+    class SetCanvasParamStimulus(Stimulus):
+        """Set the value of a canvas parameter"""
+
+        frequency = 1.0
+        
+        def __call__(self, canvas):
+            """Apply stimulus"""
+            from random import random
+            import math
+            
+            report = generator.StimulusReport(tag=self.name)
+        
+            # Read a parameter
+            rnd = random()
+            i_rnd = int(math.floor(rnd*len(canvas.params)))
+            par_name = canvas.params.keys()[i_rnd]
+            
+            # Get current value
+            current = canvas.getParam(par_name)
+            
+            # Set new value
+            rnd2 = random()
+            new_value = current*(1.0+rnd2)
+            canvas.setParam(par_name, new_value)
+            
+            # Read it back
+            current = canvas.getParam(par_name)
+            
+            # Check that the new value is correct
+            if current == new_value:
+                report.passed = 1
+            else:
+                report.log = "get: bad value for [%s]" % par_name
+            
+            report.trace = "Read %s" % par_name
+            
+            return canvas, report
+    
+    class SetShapeParamStimulus(Stimulus):
+        """Set the value of a canvas parameter"""
+
+        frequency = 1.0
+        
+        def __call__(self, canvas):
+            """Apply stimulus"""
+            from random import random
+            import math
+            
+            report = generator.StimulusReport(tag=self.name)
+        
+            # Read a parameter
+            rnd = random()
+            shape_list = canvas.getShapeList()
+
+            if len(shape_list)==0:
+                # No object available, let's test an error condition
+                try:
+                    canvas.setParam("shape0.test-radius", 1.0)
+                    report.log = "SetShapeParam: set didn't throw exception"
+                except:
+                    report.passed = 1
+                report.trace = "SetShapeParam: testing failure behavior"
+                
+            else:  
+                i_rnd = int(math.floor(rnd*len(shape_list)))
+                shape_name = shape_list[i_rnd]
+                
+                found = False
+                while found==False:
+                    rnd2 = random()
+                    par_keys = canvas.shapes[shape_name].params.keys()
+                    i_rnd2 = int(math.floor(rnd2*(len(par_keys))))
+                    
+                    short_name = par_keys[i_rnd2]
+                    par_name = "%s.%s" % (shape_name, short_name)
+                    if not short_name.lower() == "type":
+                        found= True
+                
+                # Get current value
+                current = canvas.getParam(par_name)
+                
+                # Set new value
+                if short_name in ['orientation', 'center']:
+                    new_value = [random(), random(), random()]
+                else:
+                    new_value = current*(1.0+random())
+
+                canvas.setParam(par_name, new_value)
+                
+                # Read it back
+                current = canvas.getParam(par_name)
+                
+                # Check that the new value is correct
+                if current == new_value:
+                    report.passed = 1
+                else:
+                    report.log = "get: bad value for [%s]" % par_name
+                
+                report.trace = "Read %s" % par_name
+            
+            return canvas, report
+    
+
+    class RemoveStimulus(Stimulus):
+        """Calculate I(q)"""
+
+        frequency = 1.0
+        
+        def __call__(self, canvas):
+            from random import random
+            import math
+            
+            report = generator.StimulusReport(tag=self.name)
+            
+            # Get list of shapes
+            if len(canvas.shapes)>0:
+                rnd = random()
+                i_rnd = int(math.floor(rnd*len(canvas.shapes)))
+                shape_name = canvas.shapes.keys()[i_rnd]
+                
+                canvas.delete(shape_name)
+                if shape_name in canvas.shapes.keys() \
+                    or shape_name in canvas.getShapeList():
+                    report.log = "Remove: object was not removed"
+                else:
+                    report.passed = 1
+                
+            else:
+                # No shape to remove: test bad remove call
+                try:
+                    canvas.delete("test-shape")
+                    report.log = "Remove: delete didn't throw exception"
+                except:
+                    report.passed = 1
+                report.trace = "Remove: testing failure behavior"
+            
+            return canvas, report
+    
+
+
+if __name__ == '__main__':
+    import sys
+    
+    stimuli = Simulation()
+    
+    if len(sys.argv)>1:
+        #t = generator.TestCase(stimuli, filename = "test-case.xml")
+        t = generator.TestCase(stimuli, filename = sys.argv[1])
+        print "Test passed =", t.run() 
+    else:   
+        g = generator.TestCaseGenerator(stimuli)
+        g.generateAndRun(200)
+    
+
+    
+    
Index: sansrealspace/src/realspace/test/sphere_d=0.02_Iq.txt
===================================================================
--- sansrealspace/src/realspace/test/sphere_d=0.02_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/sphere_d=0.02_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,27 @@
+PARS: {'scale': 1.0, 'radius': 20, 'contrast': 1.0, 'background': 0.0}
+<q>  <ana>  <sim>  <err>
+0.001  3.35076e+012  3.34473e+012  1.83791e+010
+0.0217917  3.22578e+012  3.22005e+012  1.77877e+010
+0.0425833  2.89407e+012  2.8891e+012  1.62491e+010
+0.063375  2.41141e+012  2.40742e+012  1.40968e+010
+0.0841667  1.85421e+012  1.85115e+012  1.1754e+010
+0.104958  1.30194e+012  1.29955e+012  9.59078e+009
+0.12575  8.20339e+011  8.18306e+011  7.82331e+009
+0.146542  4.50004e+011  4.4813e+011  6.51456e+009
+0.167333  2.02695e+011  2.0101e+011  5.63882e+009
+0.188125  6.53019e+010  6.40741e+010  5.12743e+009
+0.208917  9.08587e+009  8.72085e+009  4.88519e+009
+0.229708  6.52867e+008  1.518e+009  4.80599e+009
+0.2505  1.12169e+010  1.34659e+010  4.7969e+009
+0.271292  2.19267e+010  2.53926e+010  4.79648e+009
+0.292083  2.47336e+010  2.89354e+010  4.77778e+009
+0.312875  1.98194e+010  2.4082e+010  4.73926e+009
+0.333667  1.14538e+010  1.5099e+010  4.69233e+009
+0.354458  4.1655e+009  6.7031e+009  4.65023e+009
+0.37525  4.46529e+008  1.69842e+009  4.62081e+009
+0.396042  2.84717e+008  4.01262e+008  4.60452e+009
+0.416833  2.03152e+009  1.40088e+009  4.59665e+009
+0.437625  3.7442e+009  2.83466e+009  4.59136e+009
+0.458417  4.22545e+009  3.42181e+009  4.58465e+009
+0.479208  3.36846e+009  2.86049e+009  4.57542e+009
+0.5  1.85693e+009  1.61315e+009  4.56486e+009
Index: sansrealspace/src/realspace/test/sphere_d=0.1_Iq.txt
===================================================================
--- sansrealspace/src/realspace/test/sphere_d=0.1_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/sphere_d=0.1_Iq.txt	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,27 @@
+PARS: {'scale': 1.0, 'radius': 20, 'contrast': 1.0, 'background': 0.0}
+<q>  <ana>  <sim>  <err>
+0.001  3.35076e+012  3.34973e+012  3.67891e+009
+0.0217917  3.22578e+012  3.22395e+012  3.55965e+009
+0.0425833  2.89407e+012  2.89029e+012  3.24964e+009
+0.063375  2.41141e+012  2.40527e+012  2.81671e+009
+0.0841667  1.85421e+012  1.84622e+012  2.34653e+009
+0.104958  1.30194e+012  1.29329e+012  1.91348e+009
+0.12575  8.20339e+011  8.12471e+011  1.56046e+009
+0.146542  4.50004e+011  4.4408e+011  1.29968e+009
+0.167333  2.02695e+011  1.99229e+011  1.12572e+009
+0.188125  6.53019e+010  6.40562e+010  1.02452e+009
+0.208917  9.08587e+009  9.2613e+009  9.76591e+008
+0.229708  6.52867e+008  1.25811e+009  9.60446e+008
+0.2505  1.12169e+010  1.14561e+010  9.57674e+008
+0.271292  2.19267e+010  2.14459e+010  9.56503e+008
+0.292083  2.47336e+010  2.36485e+010  9.52061e+008
+0.312875  1.98194e+010  1.85473e+010  9.4432e+008
+0.333667  1.14538e+010  1.0453e+010  9.35555e+008
+0.354458  4.1655e+009  3.7087e+009  9.28183e+008
+0.37525  4.46529e+008  5.22496e+008  9.23435e+008
+0.396042  2.84717e+008  6.49244e+008  9.21063e+008
+0.416833  2.03152e+009  2.35982e+009  9.19933e+008
+0.437625  3.7442e+009  3.79697e+009  9.18882e+008
+0.458417  4.22545e+009  3.9482e+009  9.17274e+008
+0.479208  3.36846e+009  2.88727e+009  9.15122e+008
+0.5  1.85693e+009  1.38828e+009  9.12848e+008
Index: sansrealspace/src/realspace/test/test-case.xml
===================================================================
--- sansrealspace/src/realspace/test/test-case.xml	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/test-case.xml	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<TestCase>
+  <Stimulus id="Add"/>
+  <Stimulus id="GetIq"/>
+</TestCase>
Index: sansrealspace/src/realspace/test/test.pdb
===================================================================
--- sansrealspace/src/realspace/test/test.pdb	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/test.pdb	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,10 @@
+REMARK FILENAME="if0.pdb"
+REMARK  TOPH19.pep -MACRO for protein sequence
+REMARK DATE:17-Aug-02  11:05:39       created by user:
+ATOM      1  CB  SER     1      -1.115 -11.617 -11.368
+ATOM      2  OG  SER     1       0.202 -11.845 -10.894  1.00 54.67          
+ATOM      3  C   SER     1      -2.076 -12.837  -9.369  1.00 44.10          
+ATOM      4  O   SER     1      -3.108 -12.947  -8.704  1.00 43.77          
+ATOM      5  N   SER     1      -3.465 -12.399 -11.363  1.00 83.41          
+ATOM      6  CA  SER     1      -2.108 -12.709 -10.900  1.00 57.67          
+END
Index: sansrealspace/src/realspace/test/testcase_generator.py
===================================================================
--- sansrealspace/src/realspace/test/testcase_generator.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/testcase_generator.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,321 @@
+"""
+    Random test-case generator for Real-space simulation
+    
+    @copyright: University of Tennessee, 2007
+    @license: This software is provided as part of the DANSE project.
+"""
+import time
+
+class Stimulus:
+    """Base class for simuli objects"""
+    
+    frequency = 1.0
+    
+    def __init__(self):
+        """Initialization"""
+        name = self.__class__.__name__
+        pos = name.index('Stimulus')        
+        self.name = name[0:pos]
+        
+    def __call__(self, obj):
+        """
+            Stimulus application
+            @param obj: object to apply the stimulus to
+            @return: the modified object and the StimulusReport object
+        """
+        return obj, None
+    
+
+class StimulusReport:
+    """Report on the application of a stimulus"""
+    def __init__(self, tag=''):
+        """   
+            Initialization
+            @param tag: name of the applied stimulus
+        """
+        self.tag = tag
+        self.applied = 1
+        self.passed  = 0
+        self.trace = ''
+        self.log = ''
+        
+    def __str__(self):
+        """Returns a string with the success rate"""
+        return str(self.passed/self.applied)
+    
+    def __add__(self, other):
+        """Adds two reports"""
+        self.applied += other.applied
+        self.passed  += other.passed
+        if len(other.trace)>0:
+            self.trace = self.trace+'\n'+other.trace
+        if len(other.log)>0:
+            self.log = self.log+'\n'+other.log
+        return self
+
+    def isPassed(self):
+        """Returns true if report is a success"""
+        if self.applied == self.passed:
+            return True
+        return False
+
+
+class ReportCard: # pylint: disable-msg=R0902
+    """ Class to hold test-case results """
+    
+    def __init__(self):
+        """ Initialization """
+        ## Number of test cases
+        self.n_cases = 0
+        ## Number of passed test cases
+        self.n_cases_pass = 0
+        
+        ## Dictionary of stimuli
+        self.stimuli = {}
+        
+        ## Test log
+        self.log = ""
+        ## Trace
+        self.trace = ""
+        ## Model tested
+        self.model = ""
+        ## List of all models tested
+        self.modelList = []
+        
+        
+    def __add__(self, other):
+        """ Add two report cards
+            @param other: other report to add
+            @return: updated self
+        """
+        if other.__class__.__name__=='ReportCard':
+            # Adding two test-case report cards
+            self.n_cases += other.n_cases
+            self.n_cases_pass += other.n_cases_pass
+            
+            for tag in other.stimuli:
+                if tag in self.stimuli:
+                    self.stimuli[tag] += other.stimuli[tag]
+                else:
+                    self.stimuli[tag] = other.stimuli[tag]
+                    
+            if len(other.log)>0:
+                self.log += other.log
+            if len(other.trace)>0:
+                self.trace += other.trace
+                
+            if not other.model in self.modelList:
+                self.modelList.append(other.model)
+            
+        elif other.__class__.__name__ == 'StimulusReport':
+            # Adding a stimulus report to the report card
+            if other.tag in self.stimuli:
+                self.stimuli[other.tag] += other
+            else:
+                self.stimuli[other.tag] = other
+                    
+        else:
+            raise ValueError, \
+            "ReportCard: Unrecognized class %s" % other.__class__.__name__
+        return self
+        
+    def isPassed(self):
+        """ Returns true if no error was found """
+        if self.n_cases_pass < self.n_cases:
+            return False
+        return True
+        
+    def __str__(self):
+        """ String representation of the report card """
+        from sans.models.ModelFactory import ModelFactory
+        
+        log = ''
+        
+        rep = "Detailed output:\n"
+        rep += self.log
+        rep += "\n"
+        rep += "Total number of cases: %g\n" % self.n_cases
+        rep += "   Passed:             %g\n" % self.n_cases_pass
+        rep += "\n"
+        self.modelList.sort()
+        rep += "Models tested: %s\n" % self.modelList
+        rep += "\n"
+        rep += "\n"
+        rep += "Breakdown:\n"
+        
+        for tag in self.stimuli:
+            
+            rep += "   %-10s:          %g / %g\n" % (tag, self.stimuli[tag].passed, self.stimuli[tag].applied)
+            log += self.stimuli[tag].log
+        
+        rep += '\n'+log
+        return rep
+    
+        
+
+class TestCaseGenerator:
+    """ Generator for suite of test-cases
+    """
+    
+    def __init__(self, stimuli):
+        """ Initialization
+        """
+        self.stimuli = stimuli
+        self.n_tests = 0
+        self.n_passed = 0
+        self.time = 0
+        self.reportCard = ReportCard()
+    
+    def generateFiles(self, number, file_prefix):
+        """ Generate test-case files
+            @param number: number of files to generate
+            @param file_prefix: prefix for the file names
+        """
+        
+        for i in range(number):
+            filename = "%s_%d.xml" % (file_prefix, i)
+            self.generateFileTest(filename)
+            self.n_tests += 1
+            
+    def generateAndRun(self, number):
+        """ Generate test-cases and run them
+            @param number: number of test-cases to generate
+        """
+        start_time = time.time()
+        for i in range(number):
+            textcase = self.generateTest()
+            t = TestCase(self.stimuli, text = textcase)
+            passed = t.run()
+            self.reportCard += t.reportCard
+            if not passed:
+                t = time.localtime()
+                xmloutput = open("error_%i-%i-%i-%i-%i-%i_%i.xml" % \
+                 (t[0],t[1],t[2],t[3],t[4],t[5],self.reportCard.n_cases),'w')
+                xmloutput.write(textcase)
+                xmloutput.close()
+
+                
+        self.time += time.time()-start_time
+        print self.reportCard        
+        
+    def generateFileTest(self, filename = "tmp.xml"):
+        """
+            Write a random test-case in an XML file
+            @param filename: name of file to write to
+        """
+        text = self.generateTest()
+        # Write test case in file
+        xmlfile = open(filename,'w')
+        xmlfile.write(text)
+        xmlfile.close()
+    
+    
+    def generateTest(self):
+        """
+            Generate an XML representation of a random test-case
+        """
+        import random
+        
+        # Reset stimuli object
+        self.stimuli.reset()
+        #t = TestCase()
+        text = "<?xml version=\"1.0\"?>\n"
+        text  += "<TestCase>\n"
+        loop = True
+        while loop:
+            
+            stimulus = self.stimuli.getRandomStimulus()
+            if stimulus == None:
+                loop = False
+            else:   
+                text += "  <Stimulus id=\"%s\"/>\n" % stimulus
+        text += "</TestCase>"
+        
+        return text
+    
+        
+class TestCase:
+    """ Test-case class """
+    
+    def __init__(self, stimuliObject, filename = None, text = None):
+        """ Initialization
+            @param filename: name of file containing the test case
+        """
+        self.filename = filename
+        self.text = text
+        self.stimuli = stimuliObject
+        self.model = stimuliObject.setup()
+        self.passed = True
+        self.reportCard = ReportCard()
+        
+    
+    def run(self):
+        """ Read the test case and execute it """
+        from xml.dom.minidom import parse
+        from xml.dom.minidom import parseString
+        
+        # Initialize report
+        self.reportCard = ReportCard()
+        self.reportCard.model = "Canvas"
+        self.reportCard.n_cases = 1
+        
+        if not self.text == None:
+            dom = parseString(self.text)
+        elif not self.filename == None:
+            dom = parse(self.filename)
+        else:
+            print "No input to parse"
+            return False
+        
+        if dom.hasChildNodes():
+            for n in dom.childNodes:
+                if n.nodeName == "TestCase":
+                    self.processStimuli(n)
+            
+        # Update report card        
+        if self.passed:
+            self.reportCard.n_cases_pass = 1
+            
+        return self.passed
+                          
+    def processStimuli(self, node):
+        """ Process the stimuli list in the TestCase node 
+            of an XML test-case file
+            @param node: test-case node
+        """
+        report = ReportCard()
+        report.trace = "%s\n" % self.model.name
+        
+        self.passed = True
+        if node.hasChildNodes():
+            for n in node.childNodes:
+                if n.nodeName == "Stimulus":
+                    
+                    s_type = None
+                    if n.hasAttributes():
+                        # Get stilumus ID
+                        for i in range(len(n.attributes)):
+                            attr_name = n.attributes.item(i).name
+                            if attr_name == "id":
+                                s_type = n.attributes.item(i).nodeValue
+                    if hasattr(self.stimuli,"%sStimulus" % s_type):
+                        stimulus = getattr(self.stimuli,"%sStimulus" % s_type)()
+                        #print s_type, self.model.name
+                        m, res = stimulus(self.model)
+                        #print "     ", m.name
+                        self.model = m
+                        if not res.isPassed():
+                            self.passed = False
+                        
+                        report += res
+                        
+                    else:
+                        print "Stimulus %s not found" % s_type
+                        
+        self.reportCard += report
+        
+        if not self.passed:
+            print report.trace
+        return self.passed
+        
+ 
Index: sansrealspace/src/realspace/test/utest_realspace.py
===================================================================
--- sansrealspace/src/realspace/test/utest_realspace.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
+++ sansrealspace/src/realspace/test/utest_realspace.py	(revision ba1d1e9d2a812317a3694cc34f831cb9178b9f54)
@@ -0,0 +1,376 @@
+"""
+    Unit tests for specific models
+    @author: Mathieu Doucet / UTK
+"""
+
+import unittest, math, time
+
+# Disable "missing docstring" complaint
+# pylint: disable-msg=C0111
+# Disable "too many methods" complaint 
+# pylint: disable-msg=R0904 
+# Disable "could be a function" complaint 
+# pylint: disable-msg=R0201
+
+try:
+    import VolumeCanvas
+    print "Testing local version"
+except:
+    import sys
+    print sys.exc_value
+    #testing the version that is working on
+    print "Testing installed version"
+    import sans.realspace.VolumeCanvas as VolumeCanvas
+     
+class TestRealSpaceModel(unittest.TestCase):
+    """ Unit tests for sphere model """
+    
+    def setUp(self):
+        self.model = VolumeCanvas.VolumeCanvas()
+        self.model.add('cylinder', 'cyl')
+        self.model.add('sphere', 'sph')
+        self.model.add('ellipsoid', 'elli')
+        self.model.add('singlehelix', 'shelix')
+        
+    def testAdding(self):
+        self.assertEqual('cyl', self.model.add('cylinder', 'cyl'))   
+
+    def testDeleting(self):
+        self.model.add('ellipsoid','elli2')
+        self.model.delete('elli2')
+        self.assert_('elli2' not in self.model.getShapeList())
+
+    def testsetParam(self):
+        self.model.setParam('q_max', 0.2)
+        self.model.setParam('shelix.radius_helix', 12)
+
+    def testgetParamList(self):
+        #print self.model.getParamList()
+        #print self.model.getParamList('shelix')
+        pass
+
+    def testPr_Iq(self):
+        self.model.getPr()
+        #print "pr is calculated", self.model.hasPr
+        result = self.model.getIq(0.1)
+        #print "I(0.1) is calculated: ", result       
+
+class TestSphere(unittest.TestCase):
+    """ Unit tests for sphere model """
+
+    def setUp(self):
+        self.canvas = VolumeCanvas.VolumeCanvas()
+        
+        
+    def testSetQmax(self):
+        old_value = self.canvas.getParam('q_max')
+        new_value = old_value + 0.1
+        self.canvas.setParam('q_max', new_value)
+        self.assertEqual(self.canvas.getParam("Q_MAx"), new_value)         
+        
+    def testSetDensity(self):        
+        self.canvas.setParam('lores_density', 0.1)
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam("%s.radius" % handle, 5.0)
+        vol = 4/3*math.pi*5*5*5
+        npts_1 = vol/0.1
+        value_1 = self.canvas.getIq(0.001)
+        
+        # Change density, the answer should be the same
+        self.canvas.setParam('lores_density', 0.2)
+        npts_2 = vol/0.2
+        value_2 = self.canvas.getIq(0.001)
+        
+        self.assert_( (value_1-value_2)/value_1 < 0.1)         
+        
+    def testSetDensityTiming(self):      
+        """Testing change in computation time with density"""
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam("%s.radius" % handle, 15.0)
+        
+        self.canvas.setParam('lores_density', 0.6)
+        t_0 = time.time()
+        self.canvas.getIq(0.001)
+        t_1 = time.time()-t_0
+        
+        # Change density, the answer should be the same
+        self.canvas.setParam('lores_density', 0.1)
+        t_0 = time.time()
+        self.canvas.getIq(0.001)
+        t_2 = time.time()-t_0
+        
+        self.assert_( t_2 < t_1 and (t_1-t_2)/t_2 > 2)         
+        
+    def testGetParamList(self):
+        """ Test GetParamList on empty canvas"""
+        self.assert_('lores_density' in self.canvas.getParamList())
+        handle = self.canvas.add('sphere')
+        
+    def testGetParamListWithShape(self):
+        """ Test GetParamList on filled canvas"""
+        self.canvas.add('sphere')
+        self.assert_('lores_density' in self.canvas.getParamList())
+        
+    def testAdd(self):
+        handle = "s1"
+        self.assertEqual(handle, self.canvas.add('sphere', handle))
+ 
+        #TODO: test for current list of shape
+        self.assertEqual( [handle] , self.canvas.getShapeList())
+        
+    def testSetRadius(self):
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam("%s.rAdius" % handle, 24.0)
+        self.assertEqual(self.canvas.getParam("%s.rAdius" % handle), 24.0)
+
+    def testGetIq(self):
+        """ Test the output of I(q) to the analytical solution
+            If the normalization is wrong, we will have to fix it.
+            
+            getIq() should call getPr() behind the scenes so that
+            the user doesnt have to do it if he doesn't need to.
+        """
+        from sans.models.SphereModel import SphereModel
+        sphere = SphereModel()
+        sphere.setParam('radius', 10.0)
+        sphere.setParam('contrast', 1.0)
+        sphere.setParam('background', 0.0)
+        sphere.setParam('scale', 1.0)
+                
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam('%s.radius' % handle, 10.0)
+        self.canvas.setParam('%s.contrast' % handle, 1.0)
+        
+        
+        sim_1 = self.canvas.getIq(0.001)
+        ana_1 = sphere.run(0.001)
+        sim_2 = self.canvas.getIq(0.01)
+        ana_2 = sphere.run(0.01)
+        
+        # test the shape of the curve (calculate relative error 
+        # on the output and it should be compatible with zero
+        # THIS WILL DEPEND ON THE NUMBER OF SPACE POINTS:
+        # that why we need some error analysis.
+        self.assert_( (sim_2*ana_1/sim_1 - ana_2)/ana_2 < 0.1)
+        
+        # test the absolute amplitude
+        self.assert_( math.fabs(sim_2-ana_2)/ana_2 < 0.1)
+        
+    def testGetIq2(self):
+        """ Test two different q values
+        """
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam('%s.radius' % handle, 10.0)
+        
+        sim_1 = self.canvas.getIq(0.001)
+        sim_2 = self.canvas.getIq(0.01)
+        
+        self.assertNotAlmostEqual(sim_2, sim_1, 3)
+        
+    def testGetIq_Identical(self):
+        """ Test for identical model / no param change
+        """
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam('%s.radius' % handle, 10.0)
+        
+        sim_1 = self.canvas.getIq(0.01)
+        sim_2 = self.canvas.getIq(0.01)
+        
+        self.assertEqual(sim_2, sim_1)
+        
+    def testGetIq_Identical2(self):
+        """ Test for identical model after a parameter change
+            Should be different only of the space points
+            are regenerated and the random seed is different
+        """
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam('%s.radius' % handle, 10.0)
+        
+        self.canvas.setParam('lores_density', 0.1)
+        sim_1 = self.canvas.getIq(0.01)
+        
+        # Try to fool the code by changing to a different value
+        self.canvas.setParam('lores_density', 0.2)
+        self.canvas.getIq(0.01)
+        
+        self.canvas.setParam('lores_density', 0.1)
+        sim_2 = self.canvas.getIq(0.01)
+        
+        self.assert_((sim_2-sim_1)/sim_1<0.05)
+        
+    def testGetIq_time(self):
+        """ Time profile
+        """
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam('%s.radius' % handle, 15.0)
+        
+        
+        self.canvas.setParam('lores_density', 0.1)
+        t_0 = time.time()
+        sim_1 = self.canvas.getIq(0.01)
+        delta_1 = time.time()-t_0
+        
+        self.canvas.setParam('lores_density', 0.1)
+        
+        t_0 = time.time()
+        sim_2 = self.canvas.getIq(0.01)
+        delta_2 = time.time()-t_0
+        
+        self.assert_((delta_2-delta_1)/delta_1<0.05)
+        
+        
+    def testGetPr(self):
+        """Compare the output of P(r) to the theoretical value"""
+        #TODO: find a way to compare you P(r) to the known
+        # analytical value.
+        pass
+
+    def testLogic1(self):
+        """ Test that the internal logic is set so that the user
+            get the right output after changing a parameter
+        """
+        
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam('%s.radius' % handle, 10.0)
+        result_1 = self.canvas.getIq(0.1)
+        self.canvas.setParam('%s.radius' % handle, 20.0)
+        result_2 = self.canvas.getIq(0.1)
+        self.assertNotAlmostEqual(result_1, result_2, 2)
+
+class TestCanvas(unittest.TestCase):
+    """ Unit tests for all shapes in canvas model """
+
+    def setUp(self):
+        self.canvas = VolumeCanvas.VolumeCanvas()
+        self.canvas.params['lores_density'] = 0.05
+
+    def testGetIq_cylinder(self):
+        handle = self.canvas.add('cylinder','cyl')
+        self.canvas.setParam('%s.radius' % handle, 15.0)
+        self.canvas.setParam('%s.length' % handle, 50.0)
+        self.assertEqual(50,self.canvas.getParam('%s.length'%handle))
+        result_1 = self.canvas.getIq(0.1)
+        result_2 = self.canvas.getIq(0.1)
+        self.assertEqual(result_1,result_2)
+
+        self.canvas.delete(handle)
+        handle2 = self.canvas.add('cylinder','cyl2')
+        self.assertEqual(40,self.canvas.getParam('%s.length'%handle2))
+        result_3 = self.canvas.getIq(0.1)
+        self.assertNotEqual(result_1, result_3)
+
+    def testGetIq_ellipsoid(self):
+        handle = self.canvas.add('ellipsoid','elli')
+        self.canvas.setParam('%s.radius_x' % handle, 35)
+        self.canvas.setParam('%s.radius_y' % handle, 20)
+        self.canvas.setParam('%s.radius_z' % handle, 10)
+        result_1 = self.canvas.getIq(0.1)
+        result_2 = self.canvas.getIq(0.1)
+        self.assertEqual(result_1,result_2)
+
+        self.canvas.delete(handle)
+        self.assertEqual(False,self.canvas.hasPr)
+        handle2 = self.canvas.add('ellipsoid','elli2')
+        result_3 = self.canvas.getIq(0.1)
+        self.assertNotEqual(result_1, result_3)
+
+    def testGetIq_singlehelix(self):
+        handle = self.canvas.add('singlehelix','shelix')
+        self.canvas.setParam('%s.radius_helix' % handle, 11)
+        self.canvas.setParam('%s.radius_tube' % handle, 4)
+        self.canvas.setParam('%s.pitch' % handle, 30)
+        self.canvas.setParam('%s.turns' % handle, 3.2)
+        result_1 = self.canvas.getIq(0.1)
+        result_2 = self.canvas.getIq(0.1)
+        self.assertEqual(result_1,result_2)
+
+        self.canvas.delete(handle)
+        self.assertEqual(False,self.canvas.hasPr)
+        handle2 = self.canvas.add('singlehelix','shelix2')
+        result_3 = self.canvas.getIq(0.1)
+        self.assertNotEqual(result_1, result_3)
+        
+class TestOrdering(unittest.TestCase):
+    """ Unit tests for all shapes in canvas model """
+
+    def setUp(self):
+        from sans.models.CoreShellModel import CoreShellModel
+        radius = 15
+        thickness = 5
+        core_vol = 4.0/3.0*math.pi*radius*radius*radius
+        outer_radius = radius+thickness
+        shell_vol = 4.0/3.0*math.pi*outer_radius*outer_radius*outer_radius - core_vol
+        self.shell_sld = -1.0*core_vol/shell_vol
+        
+        self.canvas = VolumeCanvas.VolumeCanvas()
+        self.canvas.params['lores_density'] = 0.1
+        
+        # Core shell model
+        sphere = CoreShellModel()
+        # Core radius
+        sphere.setParam('radius', radius)
+        # Shell thickness
+        sphere.setParam('thickness', thickness)
+        sphere.setParam('core_sld', 1.0)
+        sphere.setParam('shell_sld', self.shell_sld)
+        sphere.setParam('solvent_sld', 0.0)
+        sphere.setParam('background', 0.0)
+        sphere.setParam('scale', 1.0)
+        self.sphere = sphere
+        self.radius = radius
+        self.outer_radius = outer_radius
+        
+    def set_coreshell_on_canvas(self, order1=None, order2=None):
+
+        handle = self.canvas.add('sphere')
+        self.canvas.setParam('%s.radius' % handle, self.outer_radius)
+        self.canvas.setParam('%s.contrast' % handle, self.shell_sld)
+        if not order1 == None:
+            self.canvas.setParam('%s.order' % handle, order1)
+       
+        handle2 = self.canvas.add('sphere')
+        self.canvas.setParam('%s.radius' % handle2, self.radius)
+        self.canvas.setParam('%s.contrast' % handle2, 1.0)
+        if not order2 == None:
+            self.canvas.setParam('%s.order' % handle2, order2)
+               
+        self.canvas.setParam('scale' , 1.0)
+        self.canvas.setParam('background' , 0.0)
+        
+        
+    def testDefaultOrder(self):
+        self.set_coreshell_on_canvas()
+        
+        ana = self.sphere.run(0.05)
+        val, err = self.canvas.getIqError(0.05)
+        self.assert_(math.fabs(ana-val)<2.0*err)
+        
+    def testRightOrder(self):
+        self.set_coreshell_on_canvas(3.0, 6.0)
+        
+        ana = self.sphere.run(0.05)
+        val, err = self.canvas.getIqError(0.05)
+        #print 'right', ana, val, err
+        self.assert_(math.fabs(ana-val)/ana < 1.1)
+        
+    def testWrongOrder(self):
+        from sans.models.SphereModel import SphereModel
+        self.set_coreshell_on_canvas(1, 0)
+        
+        # Core shell model
+        sphere = SphereModel()
+        # Core radius
+        sphere.setParam('radius', self.outer_radius)
+        # Shell thickness
+        sphere.setParam('contrast', self.shell_sld)
+        sphere.setParam('background', 0.0)
+        sphere.setParam('scale', 1.0)
+        
+        ana = sphere.run(0.05)
+        val, err = self.canvas.getIqError(0.05)
+        #print 'wrong', ana, val, err
+        self.assert_(math.fabs(ana-val)/ana < 1.1)
+            
+        
+if __name__ == '__main__':
+    unittest.main()
