Index: sansmodels/src/sans/models/test/SmearList.py
===================================================================
--- sansmodels/src/sans/models/test/SmearList.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/SmearList.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,197 @@
+
+from sans.models.ModelFactory import ModelFactory
+from sans.models.BaseComponent import BaseComponent
+import math
+
+class Smear:
+    
+    def __init__(self, model, param, sigma):
+        """
+            @param model: model to smear
+            @param param: parameter to smear
+            @param sigma: std deviations for parameter
+        """
+
+        
+        ## Model to smear
+        #self.model = factory.getModel(model_name)
+        self.model = model
+        ## Standard deviation of the smearing
+        self.sigmas = sigma
+        ## Parameter to smear
+        self.params = param
+        ## Nominal value of the smeared parameter
+        self.centers = []
+        ## Error on last evaluation
+        self.error = 0.0
+        ## Error flag
+        self.doErrors = False
+        for par in self.params:
+            self.centers.append(self.model.getParam(par))
+        
+    def smearParam(self, id, x):
+        """
+            @param x: input value
+        """
+        # from random import random
+        # If we exhausted the parameter array, simply evaluate
+        # the model
+        if id < len(self.params):
+            #print "smearing", self.params[id]
+            
+            # Average over Gaussian distribution (2 sigmas)
+            value_sum = 0.0
+            gauss_sum = 0.0
+            
+            min_value = self.centers[id] - 2*self.sigmas[id]
+            max_value = self.centers[id] + 2*self.sigmas[id]
+            n_pts = 25
+            step = (max_value - min_value)/(n_pts-1)
+            #print min_value, max_value, step, max_value-min_value
+            if step == 0.0:
+                return self.smearParam(id+1,x)
+            
+            # Gaussian function used to weigh points
+            #gaussian = ModelFactory().getModel("Gaussian")
+            gaussian = Gaussian()
+            gaussian.setParam('sigma', self.sigmas[id])
+            gaussian.setParam('mean', self.centers[id])
+                    
+            # Compute average
+            prev_value = None
+            error_sys = 0.0
+            for i in range(n_pts):
+                # Set the parameter value           
+                value = min_value + i*step
+                # value = random()*4.0*self.sigmas[id] + min_value
+                # print value
+                self.model.setParam(self.params[id], value)
+                gauss_value = gaussian.run(value)
+                #gauss_value = 1.0
+                #if id==0: print value, gauss_value
+                func_value, error_1 = self.smearParam(id+1, x)
+                if self.doErrors:
+                    if not prev_value == None:
+                        error_sys += (func_value-prev_value)*(func_value-prev_value)/4.0
+                    prev_value = func_value
+
+                value_sum += gauss_value * func_value
+                gauss_sum += gauss_value
+                
+            #print "Error", math.sqrt(error)
+            return value_sum/gauss_sum, math.sqrt(error_sys)
+        
+        else:
+            return self.model.run(x), 0.0
+        
+    def run(self, x):
+        """
+            @param x: input
+        """
+        
+        # Go through the list of parameters
+        n_par = len(self.params)
+        
+        # Check array lengths
+        if not len(self.centers) == n_par or\
+            not len(self.sigmas) == n_par:
+            raise ValueError, "Incompatible array lengths"
+        
+        # Smear first parameters
+        if n_par > 0:
+            value, error = self.smearParam(0, x)
+            self.error = error
+            
+            # Put back original values
+            for i in range(len(self.centers)):
+                self.model.setParam(self.params[i], self.centers[i])
+            
+            
+            return value
+        
+class Gaussian(BaseComponent):
+    """ Gaussian function """
+    
+    def __init__(self):
+        """ Initialization """
+        BaseComponent.__init__(self)
+        
+        ## Name of the model
+        self.name = "Gaussian"
+        ## Scale factor
+        self.params['scale']  = 1.0
+        ## Mean value
+        self.params['mean']   = 0.0
+        ## Standard deviation
+        self.params['sigma']  = 1.0
+        ## Internal log
+        self.log = {}
+        return
+    
+    def run(self, x=0):
+        """ Evaluate the function 
+            @param x: input q or [q,phi]
+            @return: scattering function
+        """
+        if(type(x)==type([])):
+            # vector input
+            if(len(x)==2):
+                return self.analytical_2D(x)
+            else:
+                raise ValueError, "Gaussian takes a scalar or a 2D point"
+        else:
+            return self.analytical_1D(x)
+
+    def analytical_2D(self, x):
+        """ Evaluate 2D model 
+            @param x: input [q,phi]
+            @return: scattering function
+        """
+        
+        # 2D sphere is the same as 1D sphere
+        return self.analytical_1D(x[0])
+
+    def analytical_1D(self, x):
+        """ Evaluate 1D model
+            @param x: input q
+            @return: scattering function
+        """
+        vary = x-self.params['mean']
+        expo_value = -vary*vary/(2*self.params['sigma']*self.params['sigma'])
+        value = self.params['scale'] *  math.exp(expo_value)
+        
+        return value
+        
+# main
+if __name__ == '__main__':
+    import math
+    if True:
+        cyl = ModelFactory().getModel("CylinderModel")
+        cyl.setParam('length', 2000.0)
+        cyl.setParam('cyl_phi', .45)
+        cyl.setParam('cyl_theta', 2.)
+        #app = Smear(cyl,['cyl_theta', 'cyl_phi'], [.01, .01])
+        #app = Smear(cyl,['cyl_theta', 'cyl_phi'], [2.0, 2.0])
+        #app = Smear(cyl,['cyl_theta'], [2.5])
+        app = Smear(cyl, ['cyl_phi', 'cyl_theta'], [math.pi, math.pi])
+        #app = Smear(cyl,['scale', 'scale'],[.5,1.0])
+        val_no = cyl.run([.001, 1.0])
+        print "Cylinder (no smear) f(.1):", val_no
+        val_sm =  app.run([.001, 1.0])
+        print "Cylinder (smear)    f(.1):", val_sm, app.error, app.error/val_sm
+        print "Cylinder (1D)    f(.1):", cyl.run(.001), cyl.run(.001)/val_sm
+      
+      
+    print "--------------"
+    sinsin = ModelFactory().getModel("SinSin")
+    sinsin.setParam('a', -1.7)
+    sinsin.setParam('b', 5.45)
+    #app = Smear(sinsin, ['A'], [3.1])
+    #app = Smear(sinsin, ['A', 'b'], [math.pi/2.0, .0])
+    app = Smear(sinsin, ['A'], [math.pi/2.0])
+    #app = Smear(cyl,['scale', 'scale'],[.5,1.0])
+    val_no = sinsin.run(1.0)
+    print "SinSin (no smear) :", val_no
+    val_sm =  app.run(1.0)
+    print "SinSin (smear)    : %g +- %g" % (val_sm, app.error)
+      
Index: sansmodels/src/sans/models/test/core_shell_cyl_testdata.txt
===================================================================
--- sansmodels/src/sans/models/test/core_shell_cyl_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/core_shell_cyl_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,139 @@
+pythonclass = CoreShellCylinderModel
+scale = 1
+radius = 20
+thickness = 10
+length = 400
+core_sld = 1e-6
+shell_sld = 4e-6
+solvent_sld = 1e-6
+background = 0.01
+
+DATA
+0.001	353.560132167546
+0.00105251266011778	353.353125068201
+0.00110778289970821	353.124000569665
+0.00116595552660488	352.87041875967
+0.00122718295288593	352.58979743803
+0.00129162559419317	352.279288228264
+0.00135945229002046	351.935750610798
+0.00143084074607265	351.555723763133
+0.00150597799985384	351.13539610369
+0.00158506091070502	350.67057245352
+0.00166829667557485	350.156638755392
+0.00175590337187494	349.588524324878
+0.00184811052884188	348.96066165541
+0.00194515972890305	348.2669438617
+0.00204730524062173	347.50067992682
+0.00215481468487986	346.654548021734
+0.00226796973604376	345.720547296668
+0.00238706685995004	344.689948707106
+0.00251241809064502	343.553245639307
+0.00264435184791283	342.300105348302
+0.00278321379773411	340.919322522823
+0.00292936775792964	339.398776654985
+0.00308319665136179	337.725395326242
+0.00324510350919104	335.885126033787
+0.00341551252681621	333.862919780817
+0.00359487017526494	331.642730345873
+0.00378364637094618	329.207533933654
+0.00398233570682956	326.539374789731
+0.00419145874827721	323.619443324298
+0.0044115633969232	320.42819431362
+0.00464322632617388	316.945513794367
+0.00488705449209019	313.150944275563
+0.0051436867236104	309.023978776314
+0.0054137953962797	304.544434827648
+0.00569808819387176	299.692919778901
+0.00599730996251769	294.451398290194
+0.00631224466220038	288.803871471549
+0.00664371742072679	282.737174369416
+0.00699259669556001	276.241893948161
+0.00735979654917468	269.313402842951
+0.00774627904389752	261.952994401996
+0.00815305676250721	254.169091326557
+0.00858119546119774	245.978483087029
+0.00903181686185589	237.40752600812
+0.00950610159096858	228.493214681787
+0.0100052922728602	219.284005115922
+0.010530696785364	209.840240785234
+0.0110836916864573	200.234006071171
+0.0116657258208385	190.548212990096
+0.0122783241158954	180.874724485493
+0.0129230915770094	171.311341206724
+0.0136017174926639	161.957540886819
+0.0143159798603742	152.908972976731
+0.0150677500450351	144.250886565509
+0.0158589976818897	136.05091014724
+0.0166917958369675	128.351896721801
+0.0175683264385096	121.165863476228
+0.0184908859936133	114.4703267421
+0.0194618916050726	108.208458104453
+0.0204838873041789	102.294332218686
+0.0215595507160742	96.6239575011789
+0.0226917000751195	91.0916748532794
+0.0238833016086589	85.6098975590415
+0.0251374773085249	80.1282952267494
+0.0264575131106459	74.6469601970146
+0.027846867504187	69.2177167568136
+0.0293091805927793	63.9295437880283
+0.0308482836315787	58.8787182407255
+0.0324682090651407	54.1312654799973
+0.0341732010924115	49.69226426132
+0.035967726786514	45.4991807718727
+0.0378564877984635	41.4499135076893
+0.0398444326754772	37.4589544254216
+0.0419367698261504	33.5130282576843
+0.0441389811664687	29.686116638358
+0.0464568364824087	26.0906695351733
+0.0488964085467568	22.7896027400326
+0.0514640890297529	19.7418575728741
+0.0541666052452436	16.8470027329151
+0.0570110377762212	14.0641733834676
+0.0600048390259259	11.4767624934243
+0.0631558527431166	9.2013416146032
+0.0664723345726646	7.23621720450609
+0.0699629736853145	5.47667717868581
+0.0736369155432808	3.90694235857881
+0.0775037858613269	2.64520254302911
+0.0815737158261042	1.71888244584613
+0.085857368639825	1.00616951922904
+0.0903659674578153	0.505746479270218
+0.0951113247931421	0.309316869918116
+0.100105873465356	0.308828468967212
+0.105362699174436	0.429865649540495
+0.110895574785276	0.739968811735628
+0.116718996412541	1.09493668549302
+0.122848221400442	1.46082520272542
+0.129299308296917	1.82653039088647
+0.136089158926978	2.05737723523194
+0.143235562675425	2.20629099045896
+0.150757243094979	2.15813904059397
+0.158673906961919	1.99386071154923
+0.167006295907771	1.66050707894721
+0.175776240762306	1.26841056680328
+0.185006718750238	0.836447930777851
+0.194721913691476	0.449693930627265
+0.20494727936264	0.185700967900882
+0.215709606185875	0.0512394957384413
+0.227037091419654	0.0559048063587326
+0.238959413035505	0.168095485620378
+0.251507807474183	0.311108248903106
+0.264715151485044	0.406870878371388
+0.278616048263005	0.408344224667358
+0.2932469181088	0.316510838067329
+0.308646093850034	0.178888365548314
+0.324853921273063	0.0632726105995981
+0.341912864828804	0.0166666303781699
+0.359867618889456	0.0363079940206598
+0.378765224847594	0.0772563511682604
+0.398655194364451	0.0908219781360385
+0.4195896390903	0.0626486424147243
+0.441623407196792	0.0256750929944015
+0.464814227078974	0.0110636566475161
+0.489222858603482	0.0167465728125504
+0.514913252299177	0.0199946004105147
+0.541952716907305	0.0145028620055464
+0.570412095730168	0.0104003959468734
+0.600365952240318	0.0104967016337968
+0.631892765436603	0.0102949185411522
+0.665075135458861	0.0106147900550481
Index: sansmodels/src/sans/models/test/core_shell_testdata.txt
===================================================================
--- sansmodels/src/sans/models/test/core_shell_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/core_shell_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,138 @@
+pythonclass = CoreShellModel
+scale = 1
+radius = 60
+thickness = 10
+core_sld = 1e-6
+shell_sld = 2e-6
+solvent_sld = 3e-6
+background = 0.001
+
+DATA
+0.001	381.274046971472
+0.00105251266011778	381.237899910898
+0.00110778289970821	381.19786027418
+0.00116595552660488	381.153509293754
+0.00122718295288593	381.104383244174
+0.00129162559419317	381.049968637346
+0.00135945229002046	380.989696908168
+0.00143084074607265	380.922938539463
+0.00150597799985384	380.848996566762
+0.00158506091070502	380.767099400611
+0.00166829667557485	380.676392895334
+0.00175590337187494	380.575931589119
+0.00184811052884188	380.464669030575
+0.00194515972890305	380.341447101163
+0.00204730524062173	380.204984233923
+0.00215481468487986	380.053862419402
+0.00226796973604376	379.886512882077
+0.00238706685995004	379.701200298987
+0.00251241809064502	379.49600542349
+0.00264435184791283	379.268805965744
+0.00278321379773411	379.017255571336
+0.00292936775792964	378.738760728792
+0.00308319665136179	378.430455426155
+0.00324510350919104	378.08917336769
+0.00341551252681621	377.711417552685
+0.00359487017526494	377.293327011804
+0.00378364637094618	376.830640491906
+0.00398233570682956	376.318656879007
+0.00419145874827721	375.752192152612
+0.0044115633969232	375.125532673916
+0.00464322632617388	374.432384627427
+0.00488705449209019	373.665819462742
+0.0051436867236104	372.818215222885
+0.0054137953962797	371.881193701371
+0.00569808819387176	370.845553445896
+0.00599730996251769	369.701198727091
+0.00631224466220038	368.437064721844
+0.00664371742072679	367.041039329271
+0.00699259669556001	365.499882251454
+0.00735979654917468	363.799142240043
+0.00774627904389752	361.923073744821
+0.00815305676250721	359.854554613775
+0.00858119546119774	357.575007000233
+0.00903181686185589	355.064324247026
+0.00950610159096858	352.300807257296
+0.0100052922728602	349.261114744241
+0.010530696785364	345.920232795758
+0.0110836916864573	342.251470410986
+0.0116657258208385	338.226489077737
+0.0122783241158954	333.81537607021
+0.0129230915770094	328.986772953036
+0.0136017174926639	323.708072763327
+0.0143159798603742	317.945701467637
+0.0150677500450351	311.665501484756
+0.0158589976818897	304.833237213943
+0.0166917958369675	297.415244439261
+0.0175683264385096	289.379246945833
+0.0184908859936133	280.695364337768
+0.0194618916050726	271.337334425244
+0.0204838873041789	261.283971041376
+0.0215595507160742	250.520872986184
+0.0226917000751195	239.042391027563
+0.0238833016086589	226.853846399068
+0.0251374773085249	213.973974766497
+0.0264575131106459	200.437542879074
+0.027846867504187	186.298049862679
+0.0293091805927793	171.630380485316
+0.0308482836315787	156.533223593014
+0.0324682090651407	141.131006418686
+0.0341732010924115	125.57502775677
+0.035967726786514	110.043406138698
+0.0378564877984635	94.7394031434915
+0.0398444326754772	79.8876517594616
+0.0419367698261504	65.7278357602731
+0.0441389811664687	52.5054542084486
+0.0464568364824087	40.4594948569723
+0.0488964085467568	29.8071600221693
+0.0514640890297529	20.726258352271
+0.0541666052452436	13.336494524359
+0.0570110377762212	7.68161679902745
+0.0600048390259259	3.71512224140784
+0.0631558527431166	1.29279888095064
+0.0664723345726646	0.175550519014221
+0.0699629736853145	0.0453924538681532
+0.0736369155432808	0.535923418986669
+0.0775037858613269	1.27580609656724
+0.0815737158261042	1.93998544566538
+0.085857368639825	2.29923118349953
+0.0903659674578153	2.25545560606361
+0.0951113247931421	1.85001590113763
+0.100105873465356	1.23672780785815
+0.105362699174436	0.621355016508782
+0.110895574785276	0.183149542475723
+0.116718996412541	0.00639177282429009
+0.122848221400442	0.0529088860080835
+0.129299308296917	0.193331375536727
+0.136089158926978	0.285780213218503
+0.143235562675425	0.258850751719227
+0.150757243094979	0.144682310792552
+0.158673906961919	0.0362122531752963
+0.167006295907771	0.00113663058210361
+0.175776240762306	0.0277097331697254
+0.185006718750238	0.0531103329757048
+0.194721913691476	0.0401179463806756
+0.20494727936264	0.010881272301514
+0.215709606185875	0.0011822843814875
+0.227037091419654	0.00839678281024765
+0.238959413035505	0.00914771323458895
+0.251507807474183	0.00271488007246614
+0.264715151485044	0.00119017076116013
+0.278616048263005	0.00209189028269908
+0.2932469181088	0.00118078317349688
+0.308646093850034	0.00108286779061982
+0.324853921273063	0.00100867939845478
+0.341912864828804	0.00130885256583451
+0.359867618889456	0.00111691184987341
+0.378765224847594	0.00155107403368663
+0.398655194364451	0.00179962321843482
+0.4195896390903	0.00131961072539651
+0.441623407196792	0.00251915473759882
+0.464814227078974	0.00118345764149102
+0.489222858603482	0.0026873874933769
+0.514913252299177	0.00137597045735508
+0.541952716907305	0.00210439596198283
+0.570412095730168	0.00207013597449065
+0.600365952240318	0.00107370624743048
+0.631892765436603	0.00208956797079987
+0.665075135458861	0.00152395959676715
Index: sansmodels/src/sans/models/test/cylinder_testdata.txt
===================================================================
--- sansmodels/src/sans/models/test/cylinder_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/cylinder_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,136 @@
+pythonclass = CylinderModel
+scale = 1
+radius = 20
+length = 400
+contrast = 3e-6
+background = 0.01
+
+DATA
+0.001	450.365065779858
+0.00105251266011778	450.146580159158
+0.00110778289970821	449.904721058613
+0.00116595552660488	449.637008936793
+0.00122718295288593	449.340705967062
+0.00129162559419317	449.012790215663
+0.00135945229002046	448.649927487565
+0.00143084074607265	448.248440688541
+0.00150597799985384	447.804276557381
+0.00158506091070502	447.312969632777
+0.00166829667557485	446.769603336734
+0.00175590337187494	446.16876808224
+0.00184811052884188	445.504516349513
+0.00194515972890305	444.770314725176
+0.00204730524062173	443.958992965425
+0.00215481468487986	443.06269023145
+0.00226796973604376	442.072798757619
+0.00238706685995004	440.979905355641
+0.00251241809064502	439.773731337189
+0.00264435184791283	438.443071660573
+0.00278321379773411	436.975734382002
+0.00292936775792964	435.358481827868
+0.00308319665136179	433.576975311161
+0.00324510350919104	431.615725703122
+0.00341551252681621	429.45805275105
+0.00359487017526494	427.086056715235
+0.00378364637094618	424.480606690776
+0.00398233570682956	421.621350889575
+0.00419145874827721	418.48675518485
+0.0044115633969232	415.054177358106
+0.00464322632617388	411.299985718283
+0.00488705449209019	407.199732049438
+0.0051436867236104	402.728390128864
+0.0054137953962797	397.860672252773
+0.00569808819387176	392.571437181701
+0.00599730996251769	386.836203490749
+0.00631224466220038	380.631782234131
+0.00664371742072679	373.937041785537
+0.00699259669556001	366.733815283289
+0.00735979654917468	359.007956784618
+0.00774627904389752	350.750545416413
+0.00815305676250721	341.959226825518
+0.00858119546119774	332.639667374524
+0.00903181686185589	322.807078144569
+0.00950610159096858	312.487742427288
+0.0100052922728602	301.720451940157
+0.010530696785364	290.557724098633
+0.0110836916864573	279.066637024047
+0.0116657258208385	267.329083828761
+0.0122783241158954	255.441218487576
+0.0129230915770094	243.511850295617
+0.0136017174926639	231.659553491498
+0.0143159798603742	220.008306831172
+0.0150677500450351	208.681580188645
+0.0158589976818897	197.79495631257
+0.0166917958369675	187.447625061693
+0.0175683264385096	177.713411852586
+0.0184908859936133	168.632377005123
+0.0194618916050726	160.204390996683
+0.0204838873041789	152.38635368558
+0.0215595507160742	145.094741759756
+0.0226917000751195	138.214768436832
+0.0238833016086589	131.616467303681
+0.0251374773085249	125.176404569476
+0.0264575131106459	118.801620763363
+0.027846867504187	112.450266533729
+0.0293091805927793	106.142075700885
+0.0308482836315787	99.9524453550689
+0.0324682090651407	93.9874997719109
+0.0341732010924115	88.3443041234747
+0.035967726786514	83.0688232388444
+0.0378564877984635	78.1304426729725
+0.0398444326754772	73.4304494970684
+0.0419367698261504	68.8486707842003
+0.0441389811664687	64.3094541736059
+0.0464568364824087	59.8273466085108
+0.0488964085467568	55.4936826961488
+0.0514640890297529	51.4009604588362
+0.0541666052452436	47.5581111282103
+0.0570110377762212	43.8774155688553
+0.0600048390259259	40.2624605003613
+0.0631558527431166	36.7152777493163
+0.0664723345726646	33.3300934619011
+0.0699629736853145	30.1616336003056
+0.0736369155432808	27.1450621666718
+0.0775037858613269	24.2065619286448
+0.0815737158261042	21.3959787911772
+0.085857368639825	18.7909944516912
+0.0903659674578153	16.3453512004984
+0.0951113247931421	14.0105824738489
+0.100105873465356	11.8613539021756
+0.105362699174436	9.91011586652733
+0.110895574785276	8.09924233046156
+0.116718996412541	6.48721642322365
+0.122848221400442	5.07262211522658
+0.129299308296917	3.8241007701892
+0.136089158926978	2.7902311341927
+0.143235562675425	1.92376715107624
+0.150757243094979	1.25195840473638
+0.158673906961919	0.739247462316389
+0.167006295907771	0.39034943849544
+0.175776240762306	0.166847976328158
+0.185006718750238	0.0638252857300758
+0.194721913691476	0.0371817504957719
+0.20494727936264	0.0700301931370014
+0.215709606185875	0.132358655755811
+0.227037091419654	0.193912768174811
+0.238959413035505	0.239497654621447
+0.251507807474183	0.257679805850731
+0.264715151485044	0.243166233829726
+0.278616048263005	0.200630401151877
+0.2932469181088	0.142354013759544
+0.308646093850034	0.083781005054643
+0.324853921273063	0.0388578776547404
+0.341912864828804	0.0157446742258134
+0.359867618889456	0.0142232167943077
+0.378765224847594	0.0263426031970756
+0.398655194364451	0.0404298608524238
+0.4195896390903	0.0459660214615077
+0.441623407196792	0.0387976446053645
+0.464814227078974	0.0251520976537578
+0.489222858603482	0.0133956386353553
+0.514913252299177	0.0108065514382524
+0.541952716907305	0.0152710060258391
+0.570412095730168	0.0196514304875119
+0.600365952240318	0.0182845678719795
+0.631892765436603	0.0130198222806718
+0.665075135458861	0.0101535864652661
Index: sansmodels/src/sans/models/test/ellipsoid_testdata.txt
===================================================================
--- sansmodels/src/sans/models/test/ellipsoid_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/ellipsoid_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,136 @@
+pythonclass = EllipsoidModel
+scale = 1
+radius_a = 20
+radius_b = 400
+contrast = 3e-6
+background = 0.01
+
+DATA
+0.001	11808.8528968632
+0.00105251266011778	11781.7147338649
+0.00110778289970821	11751.726514487
+0.00116595552660488	11718.5978599118
+0.00122718295288593	11682.01083219
+0.00129162559419317	11641.6177848645
+0.00135945229002046	11597.0391584392
+0.00143084074607265	11547.8612513417
+0.00150597799985384	11493.6340074247
+0.00158506091070502	11433.8688737723
+0.00166829667557485	11368.0367979944
+0.00175590337187494	11295.5664527249
+0.00184811052884188	11215.8427971077
+0.00194515972890305	11128.2061110769
+0.00204730524062173	11031.951668581
+0.00215481468487986	10926.3302508532
+0.00226796973604376	10810.5497404861
+0.00238706685995004	10683.7780812871
+0.00251241809064502	10545.1479370896
+0.00264435184791283	10393.7634337298
+0.00278321379773411	10228.7094202917
+0.00292936775792964	10049.0637353733
+0.00308319665136179	9853.91300696029
+0.00324510350919104	9642.37254402161
+0.00341551252681621	9413.61088528121
+0.00359487017526494	9166.879543996
+0.00378364637094618	8901.54841182498
+0.00398233570682956	8617.14714114096
+0.00419145874827721	8313.41259076583
+0.0044115633969232	7990.34206915047
+0.00464322632617388	7648.25161351986
+0.00488705449209019	7287.83787614165
+0.0051436867236104	6910.24132742693
+0.0054137953962797	6517.10741990347
+0.00569808819387176	6110.64109931719
+0.00599730996251769	5693.64864736002
+0.00631224466220038	5269.55939642207
+0.00664371742072679	4842.41854557534
+0.00699259669556001	4416.8413977311
+0.00735979654917468	3997.91920819247
+0.00774627904389752	3591.06797139074
+0.00815305676250721	3201.81444399825
+0.00858119546119774	2835.51908599765
+0.00903181686185589	2497.04384279986
+0.00950610159096858	2190.38388293401
+0.0100052922728602	1918.29597467291
+0.010530696785364	1681.97052793915
+0.0110836916864573	1480.80647627844
+0.0116657258208385	1312.35366329635
+0.0122783241158954	1172.4806119466
+0.0129230915770094	1055.80087419096
+0.0136017174926639	956.345199427954
+0.0143159798603742	868.401686020703
+0.0150677500450351	787.373295795928
+0.0158589976818897	710.444579006174
+0.0166917958369675	636.839691442685
+0.0175683264385096	567.524137445761
+0.0184908859936133	504.366315112346
+0.0194618916050726	449.001613576719
+0.0204838873041789	401.841966414852
+0.0215595507160742	361.713843354515
+0.0226917000751195	326.376840678275
+0.0238833016086589	293.695004896232
+0.0251374773085249	262.744038847928
+0.0264575131106459	234.039847131565
+0.027846867504187	208.639158819275
+0.0293091805927793	186.837277235271
+0.0308482836315787	167.70997478874
+0.0324682090651407	150.021720507628
+0.0341732010924115	133.469048132347
+0.035967726786514	118.660929907549
+0.0378564877984635	105.851355256071
+0.0398444326754772	94.3663066816175
+0.0419367698261504	83.6578396413674
+0.0441389811664687	74.0303846471908
+0.0464568364824087	65.6488966587392
+0.0488964085467568	58.0126389704112
+0.0514640890297529	51.0164068972012
+0.0541666052452436	44.9003014032841
+0.0570110377762212	39.3721336546038
+0.0600048390259259	34.3529627586149
+0.0631558527431166	29.958497372189
+0.0664723345726646	25.9550620560306
+0.0699629736853145	22.4261198409402
+0.0736369155432808	19.2679866604944
+0.0775037858613269	16.4682212334822
+0.0815737158261042	13.9888621639924
+0.085857368639825	11.805997960264
+0.0903659674578153	9.88077806606173
+0.0951113247931421	8.21127352905187
+0.100105873465356	6.74958811344309
+0.105362699174436	5.49602638898874
+0.110895574785276	4.42590111015658
+0.116718996412541	3.51634335501236
+0.122848221400442	2.7564115317264
+0.129299308296917	2.13145505928053
+0.136089158926978	1.62491960968472
+0.143235562675425	1.22192025374086
+0.150757243094979	0.908755828531692
+0.158673906961919	0.672118368226188
+0.167006295907771	0.499015180776053
+0.175776240762306	0.376993265291224
+0.185006718750238	0.294475487249178
+0.194721913691476	0.240999006101438
+0.20494727936264	0.206976804274189
+0.215709606185875	0.184084804995347
+0.227037091419654	0.166799187053838
+0.238959413035505	0.150583285805888
+0.251507807474183	0.133330958053468
+0.264715151485044	0.113698351753489
+0.278616048263005	0.0866638841974435
+0.2932469181088	0.0612447728443078
+0.308646093850034	0.07146333544507
+0.324853921273063	0.0642081900887752
+0.341912864828804	0.0396251834721476
+0.359867618889456	0.0210274910924806
+0.378765224847594	0.0199010559646776
+0.398655194364451	0.0279754666458064
+0.4195896390903	0.0319472487548983
+0.441623407196792	0.0268350754071031
+0.464814227078974	0.0181555013515734
+0.489222858603482	0.0134732506169512
+0.514913252299177	0.0143447123658664
+0.541952716907305	0.0163992637354073
+0.570412095730168	0.0156477366167964
+0.600365952240318	0.012898427036931
+0.631892765436603	0.0114950061180364
+0.665075135458861	0.0126814563174163
Index: sansmodels/src/sans/models/test/elliptical_cylinder_testdata.txt
===================================================================
--- sansmodels/src/sans/models/test/elliptical_cylinder_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/elliptical_cylinder_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,51 @@
+pythonclass = EllipticalCylinderModel
+scale = 1
+r_minor = 20
+r_ratio = 1.5
+length = 400
+contrast = 3e-6
+background = 0.00
+
+DATA
+0.001	675.50440232505
+0.00113999241252094	674.585621554061
+0.00129958270060531	673.394210762832
+0.00148151441813353	671.850289410856
+0.00168891519571259	669.851269662357
+0.00192535050850367	667.265881328373
+0.00219488497113752	663.926958947588
+0.00250215221345301	659.622979404263
+0.00285243453830891	654.088557911805
+0.00325175373088482	646.99452081442
+0.00370697458059535	637.938887875081
+0.00422592289528669	626.441265201386
+0.00481752003652534	611.944943469895
+0.00549193628880649	593.833534033594
+0.0062607656992878	571.472138550979
+0.00713722539375944	544.286072836033
+0.00813638279533754	511.890903244197
+0.0092754146520507	474.281385973753
+0.0105739023263233	432.06585649542
+0.0120541684227461	386.686741391944
+0.0137416605411801	340.493801750454
+0.0156653887523837	296.461181585324
+0.0178584243169082	257.357995056469
+0.0203584682208548	224.472404683279
+0.0232084993023232	196.656975592684
+0.0264575131106459	171.051533112635
+0.0301613642003096	145.892282588722
+0.0343837263396336	122.492775456219
+0.0391971871413787	102.387787023423
+0.044684495933335	83.9644239362009
+0.0509399863213247	66.6884221063028
+0.0580711979002305	51.7673903991309
+0.0662007249922647	38.2008300068913
+0.0754683241945671	26.8220307031804
+0.0860333169674769	17.3310728335251
+0.0980773285669326	10.0671608021353
+0.111807410406626	5.06032753961084
+0.127459599527169	2.09807907940937
+0.14530297636393	0.74349155534215
+0.165644290571589	0.362431309457757
+0.188833234429025	0.310054845776307
+
Index: sansmodels/src/sans/models/test/load_test_model.xml
===================================================================
--- sansmodels/src/sans/models/test/load_test_model.xml	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/load_test_model.xml	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,10 @@
+<Component class='AddComponent' name='AddComponent'>
+  <operateOn>
+    <Component class='SphereModel' name='SphereModel'>
+</Component>
+  </operateOn>
+  <toAdd>
+    <Component class='SphereModel' name='SphereModel'>
+</Component>
+  </toAdd>
+</Component>
Index: sansmodels/src/sans/models/test/prototypes/test_2dsim.py
===================================================================
--- sansmodels/src/sans/models/test/prototypes/test_2dsim.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/prototypes/test_2dsim.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,82 @@
+from sans_extension.prototypes.c_models import CCanvas
+from sans.models.SphereModel import SphereModel
+from sans.models.CoreShellModel import CoreShellModel
+
+import math
+
+# Sphere ###############################
+sphere = SphereModel()
+sphere.setParam('radius',40.0)
+sphere.setParam('contrast',1.0)
+sphere.setParam('scale',1.0)
+sphere.setParam('background',0.0)
+
+can = CCanvas()
+
+print "ID", can.add()
+can.setParam(0,1,40.0)
+ 
+
+for q in [1,0.1,0.05,0.02,0.01,0.001,0.0001]:
+    ana = sphere.run([q,1.0])
+    sim = can.evaluate([q,1.0])
+
+    print "Sphere: ANA = %g   SIM = %g   (%g)" %(ana, sim, sim/ana)
+
+
+
+
+# Core-shell ###########################
+# This is a core shell model where the weight-average
+# of the SLDs is zero. The I(q) should go to zero as q->0.
+
+import time
+t_0 = time.time()
+
+sphere = CoreShellModel()
+# Core radius
+radius = 10.0
+thickness = 5.0
+sphere.setParam('radius', radius)
+# Shell thickness
+sphere.setParam('thickness', thickness)
+sphere.setParam('core_sld', 1.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
+
+sphere.setParam('shell_sld', shell_sld)
+sphere.setParam('solvent_sld',0.0)
+sphere.setParam('background',0.0)
+sphere.setParam('scale',1.0)
+
+can = CCanvas()
+
+print "ID", can.add()
+can.setParam(0,1, outer_radius)
+can.setParam(0,2, shell_sld)
+
+
+print "ID", can.add()
+can.setParam(1,1, radius)
+can.setParam(1,2, 1.0)
+
+print "VOLS inner = %g, outer = %g, shell = %g" % (core_vol, shell_vol+core_vol, shell_vol)
+
+f = open('core-shell.txt','w')
+f.write("<q_cs>   <ans_cs>   <sim_cs>\n")
+for q in [1,0.1,0.05,0.04,0.03,0.02,0.01,0.005,0.001,0.0001]:
+#for i in range(400):
+    #q = 0.0001+i*0.005
+    ana = sphere.run([q,1.0])
+    sim = can.evaluate([q,1.0])
+    f.write("%g  %g  %g\n" %(q, ana, sim))
+    print "Core-shell: q=%g  ANA = %g   SIM = %g   (%g)" %(q, ana, sim, sim/ana)
+
+f.close()
+
+
+print "Time: ", time.time()-t_0
Index: sansmodels/src/sans/models/test/prototypes/test_cyl_err2.py
===================================================================
--- sansmodels/src/sans/models/test/prototypes/test_cyl_err2.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/prototypes/test_cyl_err2.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,164 @@
+try:
+    from sans.models.prototypes.SimCylinderF import SimCylinderF
+except:
+    print "This test uses the prototypes module."
+from sans.models.CylinderModel import CylinderModel
+import time, math, random
+
+def reset(seed=0):
+    sim = SimCylinderF()
+    sim.setParam('radius', 20)
+    sim.setParam('length', 200)
+    sim.setParam('phi', 1)
+    sim.setParam('theta', 1)
+    sim.setParam('seed', math.fmod(time.time()*100+seed,10000))
+    return sim
+
+# number of times to repeat each point
+N_q = 50
+# Relative error on the simulation
+sig_val = 0.20
+
+
+counter = 0
+
+sim = reset()
+sph = CylinderModel()
+
+
+sph.setParam('radius', 20)
+sph.setParam('length', 200)
+sph.setParam('scale', 1)
+sph.setParam('contrast',1)
+sph.setParam('length', 200)
+sph.setParam('cyl_phi', 1)
+sph.setParam('cyl_theta', 1)
+
+vol = math.pi*20*20*200
+
+f = open('sim_err_cyl.txt', 'w')
+f.write("<q>  <npts>  <sigma>\n")
+
+
+
+
+for i_npts in range(1):
+
+    # Find which q has an error of sig_val
+    #print err    
+    q_sigma = 0
+    i_min = 0
+    i_max = 0
+    i_last_max = 0
+    val_min = 0
+    val_max = 0
+    last_error = 0
+    last_val = 0
+    error_vec = []
+    
+    found_it = False
+    i_vec = 0
+        
+    
+    t_0 = time.time()
+    npts = 50000
+    #npts = 50000*i_npts+1000
+    #npts = 1000 * math.pow(2.0,(i_npts+1))
+    
+    sim = reset()
+    #sim.setParam('npoints', npts)
+    
+    # Vectors of errors
+    err = []
+    for i in range(80):
+        q = 0.40*(i) /50
+        
+        
+        # For each Q, repeat the simulation N_q times
+        sum_q_ana = 0
+        sum_q_sim = 0
+        sum = 0
+        vec = []
+        for i_q in range(N_q):
+            counter += 1
+            sim = reset(counter)
+            #sim.setParam('npoints', npts)
+                    
+            simval = sim.run([q, 0])
+            sum_q_ana += sph.run([q, 0])
+            sum_q_sim += simval
+            
+            vec.append(simval)
+            
+        mean = sum_q_sim/N_q
+        for val in range(N_q):
+            diff = vec[val] - mean
+            sum += diff * diff
+        sum /= (N_q-1)
+
+        ana_value = sum_q_ana/N_q
+        sim_value = sum_q_sim/N_q
+        
+         
+        try:    
+            err_i = [q, (sim_value-ana_value)/ana_value, ana_value, math.sqrt(sum)/ana_value]
+        except:
+            err_i = [q,0,0,0]
+        
+        err.append(err_i)
+                    
+        print err_i
+    
+        
+        # update extrema
+        if val_min > err_i[2]:
+            val_min = err_i[2]
+            
+        if val_max < err_i[2]:
+            val_max = err_i[2]
+        
+        # Find a minimum, where the error changes sign
+        if last_val==val_min and err_i[2] > val_min:
+            
+            
+            #print "  min", err_i[0]
+            if i_min > 0:
+                i_plateau = i_last_max
+      
+                # check error for plateau
+                #if math.fabs(err[i_plateau][1])>sig_val:
+                if math.fabs(err[i_max][3])>sig_val:
+                    error_vec.append(err[i_plateau])
+                    t_f = time.time()
+                    pred = sph.run([err[i_plateau][0],0])
+                    print '[',t_f-t_0,']',math.pow((vol/npts),-.6666), 'n=',npts, 'q=',err[i_plateau][0], \
+                        'err=',math.fabs(err[i_plateau][1]), i_min, 'sig=',err[i_plateau][3]
+                        
+                    f.write("%10g %10g %10g\n" % (err[i_plateau][0], npts, err[i_plateau][3]))
+                    found_it = True
+                    break
+                
+            i_min = i_vec
+            # Get ready for next maximum
+            val_max = val_min
+            
+        # check if we are at max
+        elif last_val==val_max and err_i[2]<val_max:
+            #print "   max", err[i_vec-1][0], err[i_vec-1][1]
+            i_last_max = i_max
+            i_max = i_vec-1
+            # get ready for next minimum
+            val_min = val_max
+
+        last_error = err_i[1]
+        last_val = err_i[2]
+        i_vec += 1
+
+        
+    if found_it == False:
+        print "Could not complete", npts
+    #print "Npts = %g; %s" %(npts, str(len(error_vec)))
+        
+
+f.close()
+
Index: sansmodels/src/sans/models/test/prototypes/test_cyl_iq.py
===================================================================
--- sansmodels/src/sans/models/test/prototypes/test_cyl_iq.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/prototypes/test_cyl_iq.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,77 @@
+try:
+    from sans.models.prototypes.TestSphere2 import TestSphere2
+    from sans.models.prototypes.SimCylinderF import SimCylinderF
+    from sans.models.prototypes.SimCylinder import SimCylinder
+except:
+    print "This test uses the prototypes module."
+from sans.models.SphereModel import SphereModel
+from sans.models.CylinderModel import CylinderModel
+import math, time
+
+
+def test_lores():
+
+    lores = open('lores.iq', 'r')
+    f = open('lores_check.txt', 'w')
+    content = lores.read()
+    lines = content.split('\n')
+    inum = 0
+    norma = 0.
+    for line in lines:
+        toks = line.split()
+        if len(toks)==2:
+            inum += 1
+            q = float(toks[0])
+            ana = sph.run(q)
+            if inum==1:
+                continue
+            elif inum==2:
+                norma = ana/float(toks[1])
+            lor = float(toks[1])*norma
+            f.write("%g %g %g\n" % (q, ana, lor))
+    f.close()
+            
+
+#sim = TestSphere2()
+#sph = SphereModel()
+sim = SimCylinderF()
+sph = CylinderModel()
+
+sim.setParam('radius', 20)
+sim.setParam('length', 200)
+sim.setParam('phi', 1)
+sim.setParam('theta', 1)
+sim.setParam('npoints',2500)
+#print sph.params
+
+sph.setParam('radius', 20)
+sph.setParam('scale', 1)
+sph.setParam('contrast',1)
+sph.setParam('length', 200)
+sph.setParam('cyl_phi', 1)
+sph.setParam('cyl_theta', 1)
+
+#print "ANA", sph.run(0.1)
+#print "SIM", sim.run(0.1)
+
+f = open('sim_iq.txt', 'w')
+f.write("<q>  <ana>  <sim>\n")
+
+t_0 = time.time()
+
+for i in range(60):
+    q = 0.01 * (i+1) /3.0
+    #ana_value = sph.run([q,0])
+    #sim_value = sim.run([q,0])
+    ana_value = sph.run(q)
+    sim_value = sim.run(q)
+    ratio = 0
+    if sim_value>0:
+        ratio = sim_value/ana_value
+    print q, ana_value, sim_value, ratio
+    f.write("%10g %10g %10g\n" % (q, ana_value, sim_value))
+
+t_f = time.time()
+print "Time = ", t_f-t_0
+f.close()
+
Index: sansmodels/src/sans/models/test/prototypes/test_simulation.py
===================================================================
--- sansmodels/src/sans/models/test/prototypes/test_simulation.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/prototypes/test_simulation.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,275 @@
+try:
+    from sans.models.prototypes.SimCylinder import SimCylinder
+except:
+    print "This test uses the prototypes module."
+    
+from sans.models.CylinderModel import CylinderModel
+
+import os, sys
+
+def simulate(filename="simul.txt", npts=500):
+    output_file = open(filename, 'w')
+    #output_file = open("simul_phi=0_theta=1_57_l=400_r=20_9.txt", 'w')
+    
+    sim = SimCylinder()
+    sim.setParam('scale', 1.0)
+    #sim.setParam('length', 100.0)
+    #sim.setParam('radius', 40.0)
+    sim.setParam('length', 400.0)
+    sim.setParam('radius', 20.0)
+    sim.setParam('theta', 1.57)
+    sim.setParam('phi', 0.0)
+    sim.setParam('qmax', 0.2)
+    
+    output_file.write("<q_value>  <ana_value>  <sim_value>\n")
+    norma = 0
+    for i in range(npts):
+        q = 1.0/npts*(i+1)
+        
+        cyl_val = sim.run(q)
+        sim_val = sim.run([q, 0.0])
+        if i==0:
+            norma = cyl_val/sim_val
+        
+        #print q, cyl_val, sim_val, sim_val/cyl_val
+        output_file.write("%10g %10g %10g\n" % (q, cyl_val, sim_val*norma))
+        
+    output_file.close()
+     
+def create_model():
+    sim = SimCylinder()
+    sim.setParam('scale', 1.0)
+    sim.setParam('length', 100.0)
+    sim.setParam('radius', 40.0)
+    # simul and simul_2
+    #sim.setParam('theta', 1.57)
+    #sim.setParam('phi', 0.0)
+    sim.setParam('theta', 0.0)
+    sim.setParam('phi', 1.0)
+    sim.setParam('qmax', 0.2)
+    return sim
+    
+def comp(): 
+    """
+        Simple test that should give 1 for the ratio
+        of simulated and analytical
+    """
+    sim = create_model()
+    
+    ana_val = sim.run(.05)
+    sim_val = sim.run([0.05, 0])
+    print ana_val, sim_val, ana_val/sim_val
+    
+    
+#    ana_val = sim.run(.05)
+#    sim_val = sim.run([0.05, 1])
+#    print ana_val, sim_val, ana_val/sim_val
+    
+#    sim = create_model()
+#    sim.setParam('theta', 1.0)
+#    sim.setParam('phi', 1.0)   
+#    ana_val = sim.run(.05)
+#    sim_val = sim.run([0.05, 1])
+#    print ana_val, sim_val, ana_val/sim_val
+    
+#    sim = create_model()
+#    sim.setParam('theta', 1.0)
+#    sim.setParam('phi', 2.0)
+#    ana_val = sim.run(.05)
+#    sim_val = sim.run([0.05, 1])
+#    print ana_val, sim_val, ana_val/sim_val
+    
+    sim = create_model()
+    sim.setParam('theta', 1.0)
+    sim.setParam('phi', -2.0)
+    ana_val = sim.run(.05)
+    sim_val = sim.run([0.15, 1])
+    print ana_val, sim_val, ana_val/sim_val
+    
+def add_error_to_file(prefix = "", dir = "output"):
+    import os, math
+    
+    # Open standard deviation file
+    std_file = open("%s/std_simul.txt" % dir, 'r')
+    std_content = std_file.read()
+    std_lines = std_content.split('\n')
+    
+    std_values = {}
+    for line in std_lines:
+        if line == std_lines[0]:
+            continue
+        
+        toks = line.split()
+        if not len(toks) > 3:
+            continue
+
+        std_values[toks[0]] = float(toks[2])
+    
+    std_file.close()
+    
+    # Read the files
+    
+    file_list = os.listdir(dir)
+    
+    for file in file_list:
+        if file.count(prefix)>0:
+            print "Reading", file
+    
+            file_obj = open("%s/%s" % (dir, file), 'r')
+            
+            # open new file
+            file_new = open("%s/error-%s" % (dir, file), 'w')
+            file_content = file_obj.read()
+            lines = file_content.split('\n')
+            for line in lines:
+                if line == lines[0]:
+                    file_new.write(line+'\n')
+                    continue
+                    
+                toks = line.split()
+                
+                if not len(toks) == 3:
+                    file_new.write(line+'\n')
+                    continue
+            
+                error = 0
+                if toks[0].lower() in std_values:
+                    error = float(toks[2]) * std_values[toks[0].lower()]
+                    
+                file_new.write(line+" %10g\n" % error)
+                
+            file_obj.close()
+            file_new.close()
+    
+    
+    
+def std_estimate(prefix = "", dir="output"):
+    import os, math
+    
+    file_list = os.listdir(dir)
+    output_file = open("%s/std_simul.txt" % dir, 'w')
+    output_file.write("<q_value>   <std>  <frac>\n")
+    values = {}
+    
+    for file in file_list:
+        if file.count(prefix)>0:
+            print "Reading", file
+            file_obj = open("%s/%s" % (dir, file), 'r')
+            file_content = file_obj.read()
+            lines = file_content.split('\n')
+            for line in lines:
+                if line == lines[0]:
+                    continue
+                toks = line.split()
+                
+                if not len(toks) == 3:
+                    continue
+                
+                if toks[0] not in values:
+                    values[toks[0]] = []
+                    
+                values[toks[0]].append(float(toks[2]))
+                                       
+    q_list = values.keys()
+    q_list.sort()                 
+    for q in q_list:
+        num = len(values[q])
+        sum = 0
+        mean = 0
+        for val in range(num):
+            mean += values[q][val] 
+        mean /= num
+            
+        for val in range(num):
+            diff = values[q][val] - mean
+            sum += diff * diff
+        sum /= (num-1)
+        #print q, math.sqrt(sum)
+    
+        output_file.write("%10s %10g %10g %10g\n" % (q, math.sqrt(sum), math.sqrt(sum)/mean, math.sqrt(sum)/mean*100.0))
+        
+    output_file.close()
+    
+    
+    # sqrt( 1/(N-1) sum[ (x-mean)**2 ] )
+    
+def test_volume_pts():
+    import random, math, time
+    
+    print "Generating points"
+    pt_list = []
+    for i in range(50000):
+        x = random.random()
+        y = random.random()
+        z = random.random()
+        pt_list.append([x, y, z])
+        
+    # Compute list of closest distance for each point
+    print "Computing distances"
+    closest_list = []
+    t_init = time.time()
+    
+    for i in range(len(pt_list)):
+        if math.fmod(i,100)==0:
+            print i, time.time()-t_init
+            t_init = time.time()
+        pt = pt_list[i]
+        
+        min_dist = -1.0
+        for j in range(i+1, len(pt_list)):
+            pt_2 = pt_list[j]
+            #print pt_2
+            dx = pt[0]-pt_2[0]
+            dy = pt[1]-pt_2[1]
+            dz = pt[2]-pt_2[2]
+            dist = math.sqrt( dx*dx + dy*dy + dz*dz ) 
+            if min_dist<0 or dist < min_dist:
+                min_dist = dist
+             
+        closest_list.append(min_dist)        
+        
+    # Analyze list of distances
+    print "Analyzing"
+    sum = 0
+    for d in closest_list:
+        sum += d
+    average = sum/len(closest_list)
+    
+    sum = 0
+    for d in closest_list:
+        diff = d - average
+        sum += diff*diff
+    sum /= len(closest_list)
+    
+    print "%5g +/- %5g)" % (average, math.sqrt(sum))
+    
+                              
+            
+    
+if __name__ == '__main__':
+    #test_volume_pts()
+    #simulate("simul_phi=0_theta=1_57_l=400_r=20_test.txt")
+    #std_estimate("simul_phi=0_theta=1_57_l=400_r=20")
+    #add_error_to_file("l=400_r=20.txt")
+    
+    option = "error"
+    
+    #if len(sys.argv)>1:
+    if len(option)>0:
+        if option=="gen":
+            ifile = 0
+            for i in range(100):
+                ifile += 1
+                #print "simul_qmax=1_phi=0_theta=1_57_l=400_r=20_%g.txt" % ifile
+                #simulate("simul_phi=0_theta=1_57_l=400_r=20_%g.txt" % ifile)
+                print "simul_qmax=1_phi=0_theta=1_57_l=400_r=20_%g.txt" % ifile
+                simulate("output/pt5000/simul_phi=0_theta=1_57_l=400_r=20_%g.txt" % ifile)
+        elif option=="comp":
+            comp()
+        elif option=="std":
+            #std_estimate("simul_phi=0_theta=1_57_l=400_r=20")
+            std_estimate("simul_phi=0_theta=1_57_l=400_r=20", "output/pt5000")
+        elif option=="error":
+            #add_error_to_file("l=400_r=20.txt")
+            add_error_to_file("l=400_r=20_1.txt", "output/pt5000")
+            
Index: sansmodels/src/sans/models/test/prototypes/test_sphere_err2.py
===================================================================
--- sansmodels/src/sans/models/test/prototypes/test_sphere_err2.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/prototypes/test_sphere_err2.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,156 @@
+try:
+    from sans.models.prototypes.SimSphereF import SimSphereF
+except:
+    print "This test uses the prototypes module."
+from sans.models.SphereModel import SphereModel
+import time, math, random
+
+def reset(seed=0):
+    sim = SimSphereF()
+    sim.setParam('radius', 60)
+    sim.setParam('seed', math.fmod(time.time()*100+seed,10000))
+    return sim
+
+# number of times to repeat each point
+N_q = 50
+# Relative error on the simulation
+sig_val = 0.10
+
+
+counter = 0
+
+sim = reset()
+sph = SphereModel()
+
+
+sph.setParam('radius', 60)
+sph.setParam('scale', 1)
+
+vol = 4/3*math.pi*60*60*60
+
+f = open('sim_err_4.txt', 'w')
+f.write("<q>  <npts>  <sigma>\n")
+
+
+
+
+for i_npts in range(15):
+
+    # Find which q has an error of sig_val
+    #print err    
+    q_sigma = 0
+    i_min = 0
+    i_max = 0
+    i_last_max = 0
+    val_min = 0
+    val_max = 0
+    last_error = 0
+    last_val = 0
+    error_vec = []
+    
+    found_it = False
+    i_vec = 0
+        
+    
+    t_0 = time.time()
+    npts = 50000*i_npts+1000
+    #npts = 1000 * math.pow(2.0,(i_npts+1))
+    
+    sim = reset()
+    sim.setParam('npoints', npts)
+    
+    # Vectors of errors
+    err = []
+    for i in range(80):
+        q = 0.40*(i) /50
+        
+        
+        # For each Q, repeat the simulation N_q times
+        sum_q_ana = 0
+        sum_q_sim = 0
+        sum = 0
+        vec = []
+        for i_q in range(N_q):
+            counter += 1
+            sim = reset(counter)
+            sim.setParam('npoints', npts)
+                    
+            simval = sim.run([q, 0])
+            sum_q_ana += sph.run([q, 0])
+            sum_q_sim += simval
+            
+            vec.append(simval)
+            
+        mean = sum_q_sim/N_q
+        for val in range(N_q):
+            diff = vec[val] - mean
+            sum += diff * diff
+        sum /= (N_q-1)
+
+        ana_value = sum_q_ana/N_q
+        sim_value = sum_q_sim/N_q
+        
+        
+        
+        try:    
+            err_i = [q, (sim_value-ana_value)/ana_value, ana_value, math.sqrt(sum)/ana_value]
+        except:
+            err_i = [q,0,0,0]
+        
+        err.append(err_i)
+                    
+         
+    
+        
+        # update extrema
+        if val_min > err_i[2]:
+            val_min = err_i[2]
+            
+        if val_max < err_i[2]:
+            val_max = err_i[2]
+        
+        # Find a minimum, where the error changes sign
+        if last_val==val_min and err_i[2] > val_min:
+            
+            
+            #print "  min", err_i[0]
+            if i_min > 0:
+                i_plateau = i_last_max
+      
+                # check error for plateau
+                #if math.fabs(err[i_plateau][1])>sig_val:
+                if math.fabs(err[i_max][3])>sig_val:
+                    error_vec.append(err[i_plateau])
+                    t_f = time.time()
+                    pred = sph.run([err[i_plateau][0],0])
+                    print '[',t_f-t_0,']',math.pow((vol/npts),-.6666), 'n=',npts, 'q=',err[i_plateau][0], \
+                        'err=',math.fabs(err[i_plateau][1]), i_min, 'sig=',err[i_plateau][3]
+                        
+                    f.write("%10g %10g %10g\n" % (err[i_plateau][0], npts, err[i_plateau][3]))
+                    found_it = True
+                    break
+                
+            i_min = i_vec
+            # Get ready for next maximum
+            val_max = val_min
+            
+        # check if we are at max
+        elif last_val==val_max and err_i[2]<val_max:
+            #print "   max", err[i_vec-1][0], err[i_vec-1][1]
+            i_last_max = i_max
+            i_max = i_vec-1
+            # get ready for next minimum
+            val_min = val_max
+
+        last_error = err_i[1]
+        last_val = err_i[2]
+        i_vec += 1
+
+        
+    if found_it == False:
+        print "Could not complete", npts
+    #print "Npts = %g; %s" %(npts, str(len(error_vec)))
+        
+
+f.close()
+
Index: sansmodels/src/sans/models/test/prototypes/test_sphere_iq.py
===================================================================
--- sansmodels/src/sans/models/test/prototypes/test_sphere_iq.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/prototypes/test_sphere_iq.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,43 @@
+try:
+    from sans.models.prototypes.SimSphereF import SimSphereF
+except:
+    print "This test uses the prototypes module."
+from sans.models.SphereModel import SphereModel
+import math, time
+
+
+
+sim = SimSphereF()
+sph = SphereModel()
+
+sim.setParam('radius', 60)
+sim.setParam('npoints', 50000)
+
+sph.setParam('radius', 60)
+sph.setParam('scale', 1)
+
+
+f = open('sim_iq.txt', 'w')
+f.write("<q>  <ana>  <sim> <err>\n")
+
+t_0 = time.time()
+
+#for i in range(60):
+for i in range(100):
+    #q = 0.01 * (i+1) /3.0
+    q = 0.05 + 0.20*(i+1) /100
+    
+    ana_value = sph.run([q,0])
+    sim_value = sim.run([q,0])
+#    ana_value = sph.run(q)
+#    sim_value = sim.run(q)
+    ratio = 0
+    if sim_value>0:
+        ratio = sim_value/ana_value
+    print q, ana_value, sim_value, ratio
+    f.write("%10g %10g %10g %10g\n" % (q, ana_value, sim_value, (sim_value-ana_value)/ana_value))
+
+t_f = time.time()
+print "Time = ", t_f-t_0
+f.close()
+
Index: sansmodels/src/sans/models/test/sphere_testdata.txt
===================================================================
--- sansmodels/src/sans/models/test/sphere_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/sphere_testdata.txt	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,135 @@
+pythonclass = SphereModel
+scale = 1
+radius = 60
+contrast = 1e-6
+background = 0.01
+
+DATA
+0.001	90.4227444561307
+0.00105251266011778	90.4157276053359
+0.00110778289970821	90.4079550029624
+0.00116595552660488	90.3993453156916
+0.00122718295288593	90.3898084718774
+0.00129162559419317	90.3792447263187
+0.00135945229002046	90.3675436251717
+0.00143084074607265	90.3545828615347
+0.00150597799985384	90.3402270095186
+0.00158506091070502	90.3243261248614
+0.00166829667557485	90.3067141976634
+0.00175590337187494	90.287207442629
+0.00184811052884188	90.2656024098205
+0.00194515972890305	90.2416738978344
+0.00204730524062173	90.2151726495366
+0.00215481468487986	90.185822808414
+0.00226796973604376	90.1533191119972
+0.00238706685995004	90.1173237963576
+0.00251241809064502	90.0774631837544
+0.00264435184791283	90.0333239229256
+0.00278321379773411	89.9844488493339
+0.00292936775792964	89.9303324299529
+0.00308319665136179	89.8704157547851
+0.00324510350919104	89.8040810347349
+0.00341551252681621	89.7306455629486
+0.00359487017526494	89.6493550944954
+0.00378364637094618	89.5593765971608
+0.00398233570682956	89.4597903244572
+0.00419145874827721	89.3495811609297
+0.0044115633969232	89.2276291894534
+0.00464322632617388	89.0926994310602
+0.00488705449209019	88.9434307099749
+0.0051436867236104	88.7783236005616
+0.0054137953962797	88.5957274193056
+0.00569808819387176	88.393826234303
+0.00599730996251769	88.1706238779775
+0.00631224466220038	87.9239279666488
+0.00664371742072679	87.6513329544612
+0.00699259669556001	87.3502022802804
+0.00735979654917468	87.0176497061855
+0.00774627904389752	86.6505199970915
+0.00815305676250721	86.2453691549684
+0.00858119546119774	85.7984445008535
+0.00903181686185589	85.3056649962709
+0.00950610159096858	84.7626023162297
+0.0100052922728602	84.1644633324426
+0.010530696785364	83.5060748418634
+0.0110836916864573	82.7818715864011
+0.0116657258208385	81.9858888591154
+0.0122783241158954	81.1117612843166
+0.0129230915770094	80.1527296972877
+0.0136017174926639	79.1016584357033
+0.0143159798603742	77.9510657891023
+0.0150677500450351	76.6931708311579
+0.0158589976818897	75.3199603730699
+0.0166917958369675	73.8232803087866
+0.0175683264385096	72.1949561473611
+0.0184908859936133	70.4269480041077
+0.0194618916050726	68.5115456917836
+0.0204838873041789	66.4416097339716
+0.0215595507160742	64.2108640045017
+0.0226917000751195	61.8142451337187
+0.0238833016086589	59.248312629239
+0.0251374773085249	56.5117216066172
+0.0264575131106459	53.605756841905
+0.027846867504187	50.5349222351867
+0.0293091805927793	47.3075733855052
+0.0308482836315787	43.9365725134711
+0.0324682090651407	40.4399341936915
+0.0341732010924115	36.8414172059712
+0.035967726786514	33.1710025095031
+0.0378564877984635	29.4651805895993
+0.0398444326754772	25.7669546222864
+0.0419367698261504	22.1254514069004
+0.0441389811664687	18.5950234076791
+0.0464568364824087	15.2337275099316
+0.0488964085467568	12.1010856393634
+0.0514640890297529	9.2550766770957
+0.0541666052452436	6.74838567895613
+0.0570110377762212	4.62405104866114
+0.0600048390259259	2.9108039900483
+0.0631558527431166	1.61857916581746
+0.0664723345726646	0.734868643264245
+0.0699629736853145	0.222751268886955
+0.0736369155432808	0.021493170785741
+0.0775037858613269	0.0505007176184419
+0.0815737158261042	0.217030561955665
+0.085857368639825	0.427366151211124
+0.0903659674578153	0.600177099372201
+0.0951113247931421	0.679643993347096
+0.100105873465356	0.644995876938007
+0.105362699174436	0.512882672304445
+0.110895574785276	0.330050926618079
+0.116718996412541	0.156436599531048
+0.122848221400442	0.0427161530728255
+0.129299308296917	0.0102364291202494
+0.136089158926978	0.0427676396140632
+0.143235562675425	0.0962906936732686
+0.150757243094979	0.124412512424065
+0.158673906961919	0.106245316452041
+0.167006295907771	0.0581157186256502
+0.175776240762306	0.0180003127669836
+0.185006718750238	0.0120232942917672
+0.194721913691476	0.0314717764737715
+0.20494727936264	0.0445914093009115
+0.215709606185875	0.0335942223501167
+0.227037091419654	0.0143439512133508
+0.238959413035505	0.0113779690004859
+0.251507807474183	0.0214441689681625
+0.264715151485044	0.0221334416546839
+0.278616048263005	0.012423248413131
+0.2932469181088	0.0111309676391173
+0.308646093850034	0.0164251291976273
+0.324853921273063	0.0133484308747301
+0.341912864828804	0.0100937607456987
+0.359867618889456	0.0133061305111565
+0.378765224847594	0.0115497267629865
+0.398655194364451	0.0103763850351245
+0.4195896390903	0.0120165913778557
+0.441623407196792	0.0100464754133085
+0.464814227078974	0.0111896261669603
+0.489222858603482	0.0102144295433158
+0.514913252299177	0.0106974772728633
+0.541952716907305	0.0101315993523187
+0.570412095730168	0.0105409279971524
+0.600365952240318	0.0100029843668479
+0.631892765436603	0.0103719406877445
+0.665075135458861	0.0101206894788735
Index: sansmodels/src/sans/models/test/testcase_generator.py
===================================================================
--- sansmodels/src/sans/models/test/testcase_generator.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/testcase_generator.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,721 @@
+"""
+    Random test-case generator for BaseComponents
+    
+    @author: Mathieu Doucet / UTK
+    @license: This software is provided as part of the DANSE project.
+"""
+import time
+
+def randomModel():
+    """ Return a random model name """
+    from sans.models.ModelFactory import ModelFactory
+    from random import random
+    from math import floor
+    
+    model_list = ModelFactory().getAllModels()
+    ready = False
+    while not ready:
+        rnd_id = int(floor(random()*len(model_list)))
+        if model_list[rnd_id] not in ["TestSphere2", "DataModel"]:
+            ready = True
+    return model_list[rnd_id]
+
+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
+        ## Number of Evaluation calls
+        self.n_eval = 0
+        ## Number of passed Evaluation calls
+        self.n_eval_pass = 0
+        ## Number of SetParam calls
+        self.n_set = 0
+        ## Number of passed Set calls
+        self.n_set_pass = 0
+        ## Number of GetParam calls
+        self.n_get = 0
+        ## Number of passed Get calls
+        self.n_get_pass = 0
+        ## Number of load calls
+        self.n_load = 0
+        ## Number of passed Load calls
+        self.n_load_pass = 0
+        ## Number of save calls
+        self.n_save = 0
+        ## Number of passed Save calls
+        self.n_save_pass = 0
+        ## Number of Add calls
+        self.n_add = 0
+        ## Number of passed Add calls
+        self.n_add_pass = 0
+        ## Number of Sub calls
+        self.n_sub = 0
+        ## Number of passed Sub calls
+        self.n_sub_pass = 0
+        ## Number of Div calls
+        self.n_div = 0
+        ## Number of passed Div calls
+        self.n_div_pass = 0
+        ## Number of mul calls
+        self.n_mul = 0
+        ## Number of passed Mul calls
+        self.n_mul_pass = 0
+        ## 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
+        """
+        
+        self.n_cases += other.n_cases
+        self.n_cases_pass += other.n_cases_pass
+        self.n_eval += other.n_eval
+        self.n_eval_pass += other.n_eval_pass
+        self.n_set += other.n_set
+        self.n_set_pass += other.n_set_pass
+        self.n_get += other.n_get
+        self.n_get_pass += other.n_get_pass
+        self.n_load += other.n_load
+        self.n_load_pass += other.n_load_pass
+        self.n_save += other.n_save
+        self.n_save_pass += other.n_save_pass
+        self.n_add += other.n_add
+        self.n_add_pass += other.n_add_pass
+        self.n_sub += other.n_sub
+        self.n_sub_pass += other.n_sub_pass
+        self.n_mul += other.n_mul
+        self.n_mul_pass += other.n_mul_pass
+        self.n_div += other.n_div
+        self.n_div_pass += other.n_div_pass
+        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)
+            
+        return self
+        
+    def isPassed(self):
+        """ Returns true if no error was found """
+        if self.n_cases_pass < self.n_cases \
+            or self.n_save_pass < self.n_save \
+            or self.n_load_pass < self.n_load \
+            or self.n_set_pass < self.n_set \
+            or self.n_get_pass < self.n_get \
+            or self.n_eval_pass < self.n_eval \
+            or self.n_add_pass < self.n_add \
+            or self.n_sub_pass < self.n_sub \
+            or self.n_mul_pass < self.n_mul \
+            or self.n_div_pass < self.n_div:
+            return False
+        return True
+        
+    def __str__(self):
+        """ String representation of the report card """
+        from sans.models.ModelFactory import ModelFactory
+        
+        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
+        all_models = ModelFactory().getAllModels()
+        all_models.sort()
+        rep += "\n"
+        rep += "Available models: %s\n" % all_models
+        rep += "\n"
+        rep += "Breakdown:\n"
+        rep += "   Eval:          %g / %g\n" % (self.n_eval_pass, self.n_eval)
+        rep += "   Set:           %g / %g\n" % (self.n_set_pass, self.n_set)
+        rep += "   Get:           %g / %g\n" % (self.n_get_pass, self.n_get)
+        rep += "   Load:          %g / %g\n" % (self.n_load_pass, self.n_load)
+        rep += "   Save:          %g / %g\n" % (self.n_save_pass, self.n_save)
+        rep += "   Add:           %g / %g\n" % (self.n_add_pass, self.n_add)
+        rep += "   Sub:           %g / %g\n" % (self.n_sub_pass, self.n_sub)
+        rep += "   Mul:           %g / %g\n" % (self.n_mul_pass, self.n_mul)
+        rep += "   Div:           %g / %g\n" % (self.n_div_pass, self.n_div)
+        return rep
+    
+        
+
+class TestCaseGenerator:
+    """ Generator for suite of test-cases
+    """
+    
+    def __init__(self):
+        """ Initialization
+        """
+        
+        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(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
+        
+        #t = TestCase()
+        text = "<?xml version=\"1.0\"?>\n"
+        text  += "<TestCase>\n"
+        stimulus = "eval"
+    
+        loop = True
+        while loop:
+            rnd = random.random()
+            
+            # run 50%
+            if rnd <= 0.5:
+                stimulus = "eval"
+            elif rnd>0.5 and rnd<=0.7:
+                stimulus = "set"
+            elif rnd>0.7 and rnd<=0.72:
+                stimulus = "save"
+            elif rnd>0.72 and rnd<=0.74:
+                stimulus = "load"
+            elif rnd>0.74 and rnd<=0.8:
+                stimulus = "get"
+            elif rnd>0.8 and rnd<=0.81:
+                stimulus = "add"
+            elif rnd>0.81 and rnd<=0.82:
+                stimulus = "div"
+            elif rnd>0.82 and rnd<=0.83:
+                stimulus = "mul"
+            elif rnd>0.83 and rnd<=0.84:
+                stimulus = "sub"
+            else:
+                # just run and quit
+                stimulus = "eval"
+                loop = False
+                
+            text += "  <Stimulus id=\"%s\"/>\n" % stimulus
+        text += "</TestCase>"
+        
+        return text
+    
+        
+class TestCase:
+    """ Test-case class """
+    
+    def __init__(self, filename = None, text = None):
+        """ Initialization
+            @param filename: name of file containing the test case
+        """
+        #from sans.models.ModelFactory import ModelFactory
+        self.filename = filename
+        self.text = text
+        #self.model = ModelFactory().getModel(randomModel())
+        self.model = getRandomModelObject()
+        #self.model = ModelFactory().getModel("SphereModel")
+        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 = self.model.name
+        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
+        """
+        import testcase_generator as generator
+        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(generator,"%sStimulus" % s_type):
+                        stimulus = getattr(generator,"%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
+        
+    
+def evalStimulus(model):
+    """ Apply the stimulus to the supplied model object
+        @param model: model to apply the stimulus to
+        @return: True if passed, False otherwise
+    """
+    minval = 0
+    maxval = 20
+    # Call run with random input
+    import random, math
+    input_value = random.random()*(maxval-minval)+minval
+    
+    # Catch division by zero, which can happen
+    # when creating random models
+    try:
+        # Choose whether we want 1D or 2D
+        if random.random()>0.5:
+            output = model.run(input_value)
+        else:
+            output = model.run([input_value, 2*math.pi*random.random()])            
+    except ZeroDivisionError:
+        output = -1
+        
+    #print "Eval: %g" % output
+    
+    # Oracle bit - just check that we have a number...
+    passed = False
+    if math.fabs(output) >= 0: 
+        passed = True
+    
+    report = ReportCard()
+    report.n_eval = 1
+    if passed:
+        report.n_eval_pass = 1
+    else:
+        report.log = "Eval: bad output value %g\n" % output
+        
+    report.trace = "Eval(%g) = %g %i\n" % (input_value, output, passed)    
+    return model, report
+
+def setStimulus(model):
+    """ Apply the stimulus to the supplied model object
+        @param model: model to apply the stimulus to
+        @return: True if passed, False otherwize
+    """
+    # Call run with random input
+    import random, math
+    minval = 1
+    maxval = 50
+    
+    # Choose a parameter to change
+    keys = model.getParamList()
+    if len(keys)==0:
+        #print model.name+" has no params"
+        return model, ReportCard()
+    
+    p_len = len(keys)
+    i_param = int(math.floor(random.random()*p_len))
+    p_name  = keys[i_param]
+    
+    # Chose a value
+    input_val = random.random()*(maxval-minval)+minval
+    model.setParam(p_name, input_val)
+    
+    # Read back
+    check_val = model.getParam(p_name)
+    #print "Set: %s = %g" % (p_name, check_val)
+    
+    # Oracle bit
+    passed = False
+    if check_val == input_val: 
+        passed = True
+    
+    report = ReportCard()
+    report.n_set = 1
+    if passed:
+        report.n_set_pass = 1
+    else:
+        report.log = "Set: parameter %s not set properly %g <> %g\n" % \
+            (p_name, input_val, check_val)
+        
+    report.trace = "Set %s = %g %i\n" % (p_name, input_val, passed)    
+    return model, report
+
+def getStimulus(model):
+    """ Apply the stimulus to the supplied model object
+        @param model: model to apply the stimulus to
+        @return: True if passed, False otherwise
+    """
+    import random, math
+    # Choose a parameter to change
+    keys = model.getParamList()
+    if len(keys)==0:
+        #print model.name+" has no params"
+        return model, ReportCard()    
+    
+    p_len = len(keys)
+    i_param = int(math.floor(random.random()*p_len))
+    p_name  = keys[i_param]
+    
+    # Read back
+    check_val = model.getParam(p_name)
+    #print "Get: %s = %g" % (p_name, check_val)
+    
+    # Oracle bit
+    passed = False
+    if math.fabs(check_val) >= 0: 
+        passed = True
+        
+    report = ReportCard()
+    report.n_get = 1
+    if passed:
+        report.n_get_pass = 1
+    else:
+        report.log = "Get: bad value for parameter %s: %g\n" % \
+            (p_name, check_val)
+        
+    report.trace = "Get %s = %g %i\n" % (p_name, check_val, passed)    
+    return model, report
+
+def loadStimulus(model):
+    """ Apply the stimulus to the supplied model object
+        @param model: model to apply the stimulus to
+        @return: True if passed, False otherwize
+    """
+    from sans.models.ModelIO import ModelIO
+    from sans.models.ModelFactory import ModelFactory
+    factory = ModelFactory()
+    io = ModelIO(factory)
+    
+    # testModel.xml should be in the directory
+    loaded = io.load("load_test_model.xml")
+    value2 = loaded.run(1)
+    
+    # Oracle bit
+    passed = False
+    if not value2 == 0: 
+        passed = True
+        
+    report = ReportCard()
+    report.n_load = 1
+    if passed:
+        report.n_load_pass = 1
+    else:
+        report.log = "Load: bad loaded model\n"
+        
+    report.trace = "Load = %s %i\n" % (loaded.name, passed)    
+    return model, report
+
+def saveStimulus(model):
+    """ Apply the stimulus to the supplied model object
+        @param model: model to apply the stimulus to
+        @return: True if passed, False otherwize
+    """
+    from sans.models.ModelIO import ModelIO
+    from sans.models.ModelFactory import ModelFactory
+    factory = ModelFactory()
+    io = ModelIO(factory)
+    io.save(model,"testModel.xml")
+    #value = model.run(1)
+
+    # Check it 
+    loaded = io.load("testModel.xml")
+    try:
+        value2 = loaded.run(1)
+    except ZeroDivisionError:
+        value2 = -1
+    
+    # Oracle bit
+    passed = False
+    
+    # The two outputs do not need to be the same
+    # since we do not save the parameters!
+    # If it's the subtraction of two identical models, 
+    # we will also get zero!
+    #if value2 == value: 
+    #    passed = True
+    
+    passed = True
+
+        
+    report = ReportCard()
+    report.n_save = 1
+    if passed:
+        report.n_save_pass = 1
+    else:
+        report.log = "Save: bad output from saved model %g\n" % value2
+        
+    report.trace = "Save %s %i\n" % (model.name, passed)    
+    return model, report
+
+def getRandomModelObject():
+    """
+        Return a random model object
+    """
+    from sans.models.ModelFactory import ModelFactory
+    while True:
+        try:
+            modelname = randomModel()
+            add_model = ModelFactory().getModel(modelname)
+            return add_model
+        except:
+            # Don't complain when a model can't be loaded.
+            # A list of tested models will appear in the 
+            # file report, which can be compared with the 
+            # list of available models, also printed.
+            pass
+            #print "Could not load ", modelname
+    
+
+def addStimulus(model):
+    """ Apply the stimulus to the supplied model object
+        @param model: model to apply the stimulus to
+        @return: True if passed, False otherwize
+    """
+    #from sans.models.ModelFactory import ModelFactory
+    #factory = ModelFactory()
+    #add_model = factory.getModel("SphereModel")
+    add_model = getRandomModelObject()
+            
+    tmp = model + add_model
+    model = tmp
+    
+    # Oracle bit
+    passed = False
+    
+    try:
+        value2 = model.run(1)
+        value2 = float(value2)
+    except:
+        passed = False
+    
+    # If we made it this far, we have a float
+    passed = True 
+        
+    report = ReportCard()
+    report.n_add = 1
+    if passed:
+        report.n_add_pass = 1
+    else:
+        report.log = "Add: bad output from composite model\n"
+        
+    report.trace = "Div %s %i\n" % (model.name, passed)    
+    return model, report
+
+def subStimulus(model):
+    """ Apply the stimulus to the supplied model object
+        @param model: model to apply the stimulus to
+        @return: True if passed, False otherwize
+    """
+    from sans.models.ModelFactory import ModelFactory
+    from random import random
+    
+    #factory = ModelFactory()
+    #sub_model = factory.getModel("CylinderModel")
+    #sub_model = factory.getModel(randomModel())
+    sub_model = getRandomModelObject()
+
+    
+    sub_model.params["scale"] = 1.1*random()
+    tmp = model - sub_model
+    
+    # Oracle bit
+    passed = False
+    
+    try:
+        value2 = tmp.run(1)
+        value2 = float(value2)
+    except:
+        passed = False
+    
+    # If we made it this far, we have a float
+    passed = True 
+        
+    report = ReportCard()
+    report.n_sub = 1
+    if passed:
+        report.n_sub_pass = 1
+    else:
+        report.log = "Sub: bad output from composite model\n"
+
+    report.trace = "Sub %s (%g - %g = %g) %i\n" % \
+        (model.name, model.run(1), \
+         sub_model.run(1), value2, passed)                
+    return tmp, report
+
+def mulStimulus(model):
+    """ Apply the stimulus to the supplied model object
+        @param model: model to apply the stimulus to
+        @return: True if passed, False otherwize
+    """
+    #from sans.models.ModelFactory import ModelFactory
+    #factory = ModelFactory()
+    #mul_model = factory.getModel("SphereModel")
+    #mul_model = factory.getModel(randomModel())
+    mul_model = getRandomModelObject()
+    tmp = model * mul_model
+    
+    # Oracle bit
+    passed = False
+
+    try:
+        value2 = tmp.run(1)
+        value2 = float(value2)
+    except:
+        passed = False
+    
+    # If we made it this far, we have a float
+    passed = True 
+        
+    report = ReportCard()
+    report.n_mul = 1
+    if passed:
+        report.n_mul_pass = 1
+    else:
+        report.log = "Mul: bad output from composite model\n"
+        
+    report.trace = "Mul %s (%g * %g = %g) %i\n" % \
+      (model.name, model.run(1), \
+       mul_model.run(1), value2, passed)                
+    return tmp, report
+
+def divStimulus(model):
+    """ Apply the stimulus to the supplied model object
+        @param model: model to apply the stimulus to
+        @return: True if passed, False otherwise
+    """
+    #from sans.models.ModelFactory import ModelFactory
+    #factory = ModelFactory()
+    #div_model = factory.getModel("SphereModel")
+    #div_model = factory.getModel(randomModel())
+    div_model = getRandomModelObject()
+    
+    tmp = model / div_model
+    
+    # Oracle bit
+    passed = False
+    
+    try:
+        from random import random
+        input_val = 1.5 * random()
+        if div_model.run(input_val)==0:
+            print "Skipped (DIV) because denominator evaluated to zero"
+        else:
+            value2 = tmp.run(input_val)
+            value2 = float(value2)
+    except:
+        passed = False
+    
+    # If we made it this far, we have a float
+    passed = True 
+        
+    report = ReportCard()
+    report.n_div = 1
+    if passed:
+        report.n_div_pass = 1
+    else:
+        report.log = "Div: bad output from composite model\n"
+        
+    report.trace = "Div %s (%g / %g = %g) %i\n" % \
+        (model.name, model.run(1), \
+         div_model.run(1), value2, passed)                
+    return tmp, report
+
+if __name__ == '__main__':
+
+    #print randomModel()
+    g = TestCaseGenerator()
+    g.generateAndRun(2000)
+    
+    #t = TestCase(filename = "error_1.17721e+009.xml")
+    #print t.run()
+    
Index: sansmodels/src/sans/models/test/utest_basemodels.py
===================================================================
--- sansmodels/src/sans/models/test/utest_basemodels.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/utest_basemodels.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,318 @@
+"""
+    Unit tests for BaseComponent 
+    @author: Mathieu Doucet / UTK
+"""
+# Disable "missing docstring" complaint
+# pylint: disable-msg=C0111
+# Disable "too many methods" complaint 
+# pylint: disable-msg=R0904 
+
+
+import unittest, math
+from sans.models.BaseComponent import BaseComponent
+
+class ConstantComponent(BaseComponent):
+    """ Test component """
+    def __init__(self, value=0):
+        BaseComponent.__init__(self)
+        self.params['value'] = value
+        self.dim = 0
+        self.name = 'constant'
+    def run(self, x=0):
+        """ Evaluate method """
+        return self.params['value']
+        
+class IdentityComponent(BaseComponent):
+    """ Test identity component """
+    def __init__(self):
+        BaseComponent.__init__(self)
+        self.dim = 1
+        self.name = 'id'
+    def run(self, x=0):
+        """ Evaluate method """
+        return x
+        
+class SinComponent(BaseComponent):
+    """ Test sin(x) component """
+    def __init__(self):
+        BaseComponent.__init__(self)
+        self.params['norm'] = 1
+        self.dim = 1
+        self.name = 'sin'
+    def run(self, x=0):
+        """ Evaluate method """
+        return self.params['norm']*math.sin(x)
+        
+class TwoDimComponent(BaseComponent):
+    """ Test 2D component """
+    def __init__(self):
+        BaseComponent.__init__(self)
+        self.params['norm'] = 1
+        self.dim = 2
+        self.name = '2D'
+    def run(self, x):
+        """ Evaluate method """
+        return self.params['norm']*x[0]*math.sin(x[1])
+        
+class ThreeDimComponent(BaseComponent):
+    """ Test 3D component """
+    def __init__(self):
+        BaseComponent.__init__(self)
+        self.params['norm'] = 1
+        self.dim = 3
+        self.name = '3D'
+    def run(self, x):
+        """ Evaluate method """
+        return self.params['norm']*x[0]*math.sin(x[1])*x[2]*x[2]
+        
+
+
+class TestBasicComponent(unittest.TestCase):
+    
+    def setUp(self):
+        self.comp = BaseComponent()
+
+    def testrun(self):
+        input = 45
+        self.assertEqual(self.comp.run(input), 0)
+        
+class TestArithmetic(unittest.TestCase):
+    def setUp(self):
+        self.value1 = 5
+        self.comp1 = ConstantComponent(self.value1)
+        
+        self.value2 = 7
+        self.comp2 = ConstantComponent(self.value2)
+        
+    def testrun(self):
+        self.assertEqual(self.comp1.run(45), self.value1)
+        
+    def testadd(self):
+        newcomp = self.comp1+self.comp2
+        self.assertEqual(newcomp.run(45), self.value1+self.value2)
+        
+    def testmultiply(self):
+        newcomp = self.comp1*self.comp2
+        self.assertEqual(newcomp.run(45), self.value1*self.value2)
+        
+    def testsubtract(self):
+        newcomp = self.comp1-self.comp2
+        self.assertEqual(newcomp.run(45), self.value1-self.value2)
+        
+    def testdivide(self):
+        newcomp = self.comp1/self.comp2
+        self.assertEqual(newcomp.run(45), self.value1/self.value2)
+        print newcomp
+        
+    def vtestcomplic1(self):
+        val = (self.value1+self.value2)/self.value2
+        newcomp = (self.comp1+self.comp2)
+        newcomp2 = newcomp/self.comp2
+        self.assertEqual(newcomp2.run(45), val)
+
+    def testcomplic2(self):
+        # A + B 
+        newcomp = self.comp1+self.comp2
+        # Value of A
+        self.assertEqual(newcomp.operateOn.params['value'], self.value1)
+        # Value of B
+        self.assertEqual(newcomp.other.params['value'], self.value2)
+        # Value of D = A+B
+        self.assertEqual(newcomp.run(), self.value1+self.value2)
+        # D + C
+        newcomp2 = newcomp+self.comp2
+        # Value of D
+        self.assertEqual(newcomp2.operateOn.run(), self.value1+self.value2)
+        # Value of B
+        self.assertEqual(newcomp2.other.params['value'], self.value2)
+        
+        
+        self.assertEqual(newcomp2.run(45), self.value1+self.value2+self.value2)
+
+    def testcomplic3(self):
+        # Same as testcomplic2, but all in one go
+        newcomp = self.comp1+self.comp2+self.comp2
+        self.assertEqual(newcomp.run(45), self.value1+self.value2+self.value2)
+        
+    def testcomplic4(self):
+        output = (self.value1+self.value1)/(self.value1+self.value2)
+        newcomp = (self.comp1+self.comp1)/(self.comp1+self.comp2)
+        self.assertEqual(newcomp.run(45), output)
+                
+class TestIO(unittest.TestCase):
+    def setUp(self):
+        from sans.models.ModelFactory import ModelFactory
+        from sans.models.ModelIO import ModelIO
+        self.factory = ModelFactory()
+        self.io = ModelIO(self.factory)
+        self.sph = self.factory.getModel("SphereModel")
+        self.cyl = self.factory.getModel("CylinderModel")
+        
+        
+    def saveLoadAndCompare(self, combo):
+        # Save
+        self.io.save(combo,"test_io.xml")
+        # Load
+        loaded = self.io.load("test_io.xml")
+        
+        # Check that the model is the same
+        # ONLY WORKS FOR DEFAULT PARAMETERS
+        # print combo.run(1)
+        self.assertEqual(combo.run(1), loaded.run(1))
+        return loaded
+        
+    def testSimple(self):
+        self.saveLoadAndCompare(self.sph)
+        
+    def testAdd(self):
+        combo = self.sph+self.cyl
+        self.saveLoadAndCompare(combo)
+
+    def testSub(self):
+        combo = self.sph-self.cyl
+        self.saveLoadAndCompare(combo)
+
+    def testMul(self):
+        combo = self.sph*self.cyl
+        self.saveLoadAndCompare(combo)
+
+    def testDiv(self):
+        combo = self.sph/self.cyl
+        self.saveLoadAndCompare(combo)
+        
+    def testDegree2(self):
+        combo = (self.sph+self.sph)/self.cyl
+        self.saveLoadAndCompare(combo)
+        
+    def testDegree3(self):
+        combo = self.sph*(self.cyl+self.sph/self.cyl)
+        self.saveLoadAndCompare(combo)
+
+    def testReSave(self):
+        combo = self.sph+self.cyl
+        loaded = self.saveLoadAndCompare(combo)
+        self.saveLoadAndCompare(loaded)
+
+class TestGetSet(unittest.TestCase):
+    def setUp(self):
+        from sans.models.ModelFactory import ModelFactory
+        self.factory = ModelFactory()
+        self.sph = self.factory.getModel("SphereModel")
+        
+    def testGet(self):
+        # The default value is 1.0e-6
+        self.assertEqual(self.sph.getParam("scale"), 1.0e-6)
+        
+    def testGetCaseSensitivity(self):
+        # The default value is 1.0e-6
+        self.assertEqual(self.sph.getParam("sCale"), 1.0e-6)
+        
+    def testGetException(self):
+        self.assertRaises(ValueError, self.sph.getParam, "scalee")
+        
+    def testSet(self):
+        value_0 = self.sph.run(1)
+        scale = 2.0e-6
+        self.sph.setParam("scale", scale)
+        self.assertEqual(self.sph.getParam("scale"), scale)
+        self.assertEqual(self.sph.run(1), 2*value_0)
+        
+    def testSetCaseSensitivity(self):
+        value_0 = self.sph.run(1)
+        scale = 2.0e-6
+        self.sph.setParam("sCale", scale)
+        self.assertEqual(self.sph.getParam("scale"), scale)
+        self.assertEqual(self.sph.run(1), 2*value_0)
+        
+    def testSetException(self):
+        self.assertRaises(ValueError, self.sph.setParam, "scalee", 2.0)
+        
+    def testCompositeAdd(self):
+        sph2 = self.factory.getModel("SphereModel")
+        sph2.setParam("scale", 2.0e-6)
+        model = sph2+self.sph  
+        self.assertEqual(model.getParam('Base.scale'), 2.0e-6)
+        self.assertEqual(model.getParam('add.scale'), 1.0e-6)
+        
+    def testCompositeSub(self):
+        sph2 = self.factory.getModel("SphereModel")
+        sph2.setParam("scale", 2.0e-6)
+        model = sph2-self.sph  
+        self.assertEqual(model.getParam('Base.scale'), 2.0e-6)
+        self.assertEqual(model.getParam('sub.scale'), 1.0e-6)
+        
+    def testCompositeMul(self):
+        sph2 = self.factory.getModel("SphereModel")
+        sph2.setParam("scale", 2.0e-6)
+        model = sph2*self.sph  
+        self.assertEqual(model.getParam('Base.scale'), 2.0e-6)
+        self.assertEqual(model.getParam('mul.scale'), 1.0e-6)
+        
+    def testCompositeDiv(self):
+        sph2 = self.factory.getModel("SphereModel")
+        sph2.setParam("scale", 2.0e-6)
+        model = sph2/self.sph  
+        self.assertEqual(model.getParam('Base.scale'), 2.0e-6)
+        self.assertEqual(model.getParam('div.scale'), 1.0e-6)
+        
+    def testSetCompositeAdd(self):
+        sph2 = self.factory.getModel("SphereModel")
+        #sph2.setParam("scale", 2.0e-6)
+        model = sph2+self.sph  
+        value = model.run(1)
+        scale = 2.0e-6
+        model.setParam("base.scale", scale)
+        model.setParam("add.scale", scale)
+       
+        self.assertEqual(model.getParam("base.scale"), scale)
+        self.assertEqual(model.getParam("add.scale"), scale)
+        self.assertEqual(model.run(1), 2*value)
+        
+    def testSetCompositeSub(self):
+        sph2 = self.factory.getModel("SphereModel")
+        #sph2.setParam("scale", 2.0e-6)
+        model = sph2-self.sph  
+        value = model.run(1)
+        scale = 2.0e-6
+        model.setParam("base.scale", scale)
+        model.setParam("sub.scale", scale)
+       
+        self.assertEqual(model.getParam("base.scale"), scale)
+        self.assertEqual(model.getParam("sub.scale"), scale)
+        self.assertEqual(model.run(1), 2*value)
+        
+    def testSetCompositeMul(self):
+        sph2 = self.factory.getModel("SphereModel")
+        #sph2.setParam("scale", 2.0e-6)
+        model = sph2*self.sph  
+        value = model.run(1)
+        scale = 2.0e-6
+        model.setParam("mul.scale", scale)
+       
+        self.assertEqual(model.getParam("mul.scale"), scale)
+        self.assertEqual(model.run(1), 2*value)
+        
+    def testSetCompositeDiv(self):
+        sph2 = self.factory.getModel("SphereModel")
+        #sph2.setParam("scale", 2.0e-6)
+        model = sph2/self.sph  
+        value = model.run(1)
+        scale = 2.0e-6
+        model.setParam("div.scale", scale)
+       
+        self.assertEqual(model.getParam("div.scale"), scale)
+        self.assertEqual(model.run(1), 0.5*value)
+        
+    def testParamList(self):
+        sph2 = self.factory.getModel("SphereModel")
+        model = sph2/self.sph  
+        p_list = model.getParamList()
+        #print p_list
+        check_list = ['base.scale', 'base.radius', 'base.background', 
+                      'base.contrast', 
+                      'div.scale', 'div.radius', 'div.contrast', 
+                      'div.background']
+        self.assertEqual(check_list, p_list)
+         
+if __name__ == '__main__':
+    unittest.main()
Index: sansmodels/src/sans/models/test/utest_disperser.py
===================================================================
--- sansmodels/src/sans/models/test/utest_disperser.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/utest_disperser.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,106 @@
+"""
+    Unit tests for specific models
+    @author: Mathieu Doucet / UTK
+"""
+
+import unittest
+from SmearList import Smear
+
+# 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
+
+from sans_extension.c_models import Disperser
+from sans.models.ModelFactory import ModelFactory
+from sans.models.DisperseModel import DisperseModel
+      
+class TestDisperser(unittest.TestCase):
+    """ Unit tests for sphere model """
+    
+    def setUp(self):
+        self.model = ModelFactory().getModel("CylinderModel")
+        self.model.setParam("cyl_theta", 1.57)
+        self.model.setParam("cyl_phi", 0.1)
+        
+        
+
+    def testNoDisp(self):
+        """ Test 1D model for a sphere """
+        q = 0.005
+        d = Disperser(self.model, [], [])
+        value = d.run([q, 0])
+        self.assertEqual(value, self.model.run([q, 0]))
+
+    def testComp(self):
+        q = 0.005
+        phi = 0.10
+        sigma = 0.3
+        value_0 = self.model.run([q, phi])
+        app = Smear(self.model, ['cyl_phi'], [sigma])
+        val_py = app.run([q, phi])
+        
+        # Check that the parameters were returned to original values
+        self.assertEqual(value_0, self.model.run([q, phi]))
+        d = Disperser(self.model, ["cyl_phi"], [sigma])
+        val_c = d.run([q, phi])
+        self.assertEqual(val_py, val_c)
+        
+    def test2Disp(self):
+        q = 0.005
+        phi = 0.10
+        sigma = 0.3
+        value_0 = self.model.run([q, phi])
+        app = Smear(self.model, ['cyl_phi', 'cyl_theta'], [sigma, sigma])
+        val_py = app.run([q, phi])
+        
+        # Check that the parameters were returned to original values
+        self.assertEqual(value_0, self.model.run([q, phi]))
+        d = Disperser(self.model, ["cyl_phi", "cyl_theta"], [sigma, sigma])
+        val_c = d.run([q, phi])
+        self.assertEqual(val_py, val_c)
+        
+    def test3Disp(self):
+        q = 0.005
+        phi = 0.10
+        sigma = 0.3
+        value_0 = self.model.run([q, phi])
+        app = Smear(self.model, 
+                    ['cyl_phi', 'cyl_theta', 'radius'], [sigma, sigma, 1.0])
+        val_py = app.run([q, phi])
+        
+        # Check that the parameters were returned to original values
+        self.assertEqual(value_0, self.model.run([q, phi]))
+        d = Disperser(self.model, 
+                      ["cyl_phi", "cyl_theta", 'radius'], [sigma, sigma, 1.0])
+        val_c = d.run([q, phi])
+        self.assertEqual(val_py, val_c)
+        
+        
+class TestDisperserModel(unittest.TestCase):
+    """ Unit tests for sphere model """
+    
+    def setUp(self):
+        self.model = ModelFactory().getModel("CylinderModel")
+        self.model.setParam("cyl_theta", 1.57)
+        self.model.setParam("cyl_phi", 0.1)
+        
+    def test2Disp(self):
+        q = 0.005
+        phi = 0.10
+        sigma = 0.3
+        value_0 = self.model.run([q, phi])
+        app = Smear(self.model, ['cyl_phi', 'cyl_theta'], [sigma, sigma])
+        val_py = app.run([q, phi])
+        
+        # Check that the parameters were returned to original values
+        self.assertEqual(value_0, self.model.run([q, phi]))
+        d = DisperseModel(self.model, ["cyl_phi", "cyl_theta"], [sigma, sigma])
+        val_c = d.run([q, phi])
+        self.assertEqual(val_py, val_c)
+
+
+if __name__ == '__main__':
+    unittest.main()
Index: sansmodels/src/sans/models/test/utest_models.py
===================================================================
--- sansmodels/src/sans/models/test/utest_models.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/utest_models.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,246 @@
+"""
+    Unit tests for specific models
+    @author: Mathieu Doucet / UTK
+"""
+
+import unittest, time, math
+
+# 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
+
+
+        
+class TestSphere(unittest.TestCase):
+    """ Unit tests for sphere model """
+    
+    def setUp(self):
+        from sans.models.SphereModel import SphereModel
+        self.comp = SphereModel()
+        
+    def test1D(self):
+        """ Test 1D model for a sphere """
+        self.assertAlmostEqual(self.comp.run(1.0), 56.3878, 4)
+        
+    def test1D_2(self):
+        """ Test 2D model for a sphere """
+        self.assertAlmostEqual(self.comp.run([1.0, 1.3]), 56.3878, 4)
+
+class TestCyl(unittest.TestCase):
+    """Unit tests for cylinder"""
+    
+    def setUp(self):
+
+        from sans.models.CylinderModel import CylinderModel
+        self.comp = CylinderModel()
+        
+    def test1D(self):
+        """ Test 1D model of a cylinder """ 
+        self.assertAlmostEqual(self.comp.run(0.2), 0.041761386790780453, 4)
+       
+    def testTime(self):
+        """ Time profiling """
+        self.comp.run(2.0)
+        t0 = time.clock()
+        self.assertTrue(time.clock()-t0<1e-5)
+     
+    def test2D(self):
+        """ Test 2D model of a cylinder """ 
+        self.assertAlmostEqual(self.comp.run([0.2, 2.5]), 
+                               0.038176446608393366, 4)
+       
+    def testIO(self):
+        from sans.models.ModelFactory import ModelFactory
+        from sans.models.ModelIO import ModelIO
+        factory = ModelFactory()
+        io = ModelIO(factory)
+        io.save(self.comp,"myModel.xml")
+        value = self.comp.run(1)
+        loaded = io.load("myModel.xml")
+        self.assertEqual(value, loaded.run(1))
+        
+    def testIO_add(self):
+        from sans.models.ModelFactory import ModelFactory
+        from sans.models.ModelIO import ModelIO
+        factory = ModelFactory()
+        io = ModelIO(factory)
+        sph = factory.getModel("SphereModel")
+        cyl = factory.getModel("CylinderModel")
+        combo = sph - cyl
+        io.save(combo,"myModel.xml")
+        value = combo.run(1)
+        loaded = io.load("myModel.xml")
+        self.assertEqual(value, loaded.run(1))
+        
+    def testIO_add2(self):
+        from sans.models.ModelFactory import ModelFactory
+        #from sans.models.ModelIO import ModelIO
+        factory = ModelFactory()
+        #io = ModelIO(factory)
+        sph = factory.getModel("SphereModel")
+        cyl = factory.getModel("CylinderModel")
+        sph2 = factory.getModel("SphereModel")
+        combo1 = cyl - sph
+        combo = combo1 / sph2
+        #combo1 = sph
+        #io.save(combo,"myModel.xml")
+        # Just check that we have some output
+        self.assertTrue(math.fabs(combo.run(1))>0)
+        #loaded = io.load("myModel.xml")
+        #self.assertEqual(value, loaded.run(1))
+        
+        
+      
+       
+class TestFactory(unittest.TestCase):
+    """Unit tests for Model Factory"""
+    
+    def setUp(self):
+        from sans.models.ModelFactory import ModelFactory
+        self.comp = ModelFactory().getModel('CylinderModel')
+        
+    def test1D(self):
+        """ Test 1D model of a cylinder """ 
+        self.assertAlmostEqual(self.comp.run(0.2), 0.041761386790780453, 4)
+        
+class TestAddition(unittest.TestCase):
+    """Unit tests for Model Factory"""
+    
+    def setUp(self):
+        from sans.models.ModelFactory import ModelFactory
+        self.comp1 = ModelFactory().getModel('CylinderModel')
+        self.comp2 = ModelFactory().getModel('CylinderModel')
+        self.comp3 = self.comp1+self.comp2
+        
+    def test1D(self):
+        """ Test 1D model of a cylinder """
+        self.assertAlmostEqual(self.comp3.run(0.2), 2*0.041761386790780453, 4)
+        
+ 
+class TestGaussian(unittest.TestCase):
+    """Unit tests for Gaussian function"""
+    
+    def setUp(self):
+        from sans.models.Gaussian import Gaussian
+        self.gauss= Gaussian()
+        
+    def test1D(self):
+        self.gauss.setParam('scale', 2.0)
+        self.gauss.setParam('center', 1.0)
+        self.gauss.setParam('sigma', 1.0)
+        self.assertEqual(self.gauss.run(1.0), 2.0)
+        value = math.exp(-0.5)
+        self.assertEqual(self.gauss.run(2.0), 2.0*value)
+        
+    def test2D(self):
+        self.gauss.setParam('scale', 2.0)
+        self.gauss.setParam('center', 1.0)
+        self.gauss.setParam('sigma', 1.0)
+        self.assertEqual(self.gauss.runXY([1.0,1.0]), 2.0*2.0)
+        value = math.exp(-0.5)
+        self.assertEqual(self.gauss.runXY([1.0,2.0]), 2.0*2.0*value)
+        self.assertEqual(self.gauss.runXY([2.0,2.0]), 2.0*2.0*value*value)
+        
+    def test2Dphi(self):
+        self.gauss.setParam('scale', 2.0)
+        self.gauss.setParam('center', 1.0)
+        self.gauss.setParam('sigma', 1.0)
+        value = math.exp(-0.5)
+        self.assertEqual(self.gauss.run([math.sqrt(8.0), math.pi/4.0]), 2.0*2.0*value*value)
+        
+class TestLorentzian(unittest.TestCase):
+    """Unit tests for Lorentzian function"""
+    
+    def setUp(self):
+        from sans.models.Lorentzian import Lorentzian
+        self.lor= Lorentzian()
+        
+    def test1D(self):
+        self.lor.setParam('scale', 2.0)
+        self.lor.setParam('center', 1.0)
+        self.lor.setParam('gamma', 1.0)
+        self.assertEqual(self.lor.run(1.0), 4.0/math.pi)
+        value = 1/math.pi*0.5/(1+.25)
+        self.assertEqual(self.lor.run(2.0), 2.0*value)
+        
+    def test2D(self):
+        self.lor.setParam('scale', 0.5*math.pi)
+        self.lor.setParam('center', 1.0)
+        self.lor.setParam('gamma', 1.0)
+        self.assertEqual(self.lor.run(1.0), 1.0)
+        value = 0.25/(1+.25)
+        self.assertEqual(self.lor.runXY([1.0,2.0]), value)
+        self.assertEqual(self.lor.runXY([2.0,2.0]), value*value)
+        
+    def test2Dphi(self):
+        self.lor.setParam('scale', 0.5*math.pi)
+        self.lor.setParam('center', 1.0)
+        self.lor.setParam('gamma', 1.0)
+        self.assertEqual(self.lor.run(1.0), 1.0)
+        value = 0.25/(1+.25)
+        self.assertEqual(self.lor.run([math.sqrt(8.0), math.pi/4.0]), value*value)
+        
+class TestSin(unittest.TestCase):
+    """Unit tests for Sin(x) function"""
+    
+    def setUp(self):
+        from sans.models.Sin import Sin
+        self.sin = Sin()
+
+    def test1D(self):
+        self.assertEqual(self.sin.run(1.13), math.sin(1.13))
+        
+    def test2D(self):
+        self.assertEqual(self.sin.run([1.13,0.56]), math.sin(1.13*math.cos(0.56))*math.sin(1.13*math.sin(0.56)))
+        self.assertEqual(self.sin.runXY([1.13,0.56]), math.sin(1.13)*math.sin(0.56))
+        
+class TestCos(unittest.TestCase):
+    """Unit tests for Cos(x) function"""
+    
+    def setUp(self):
+        from sans.models.Cos import Cos
+        self.cos = Cos()
+
+    def test1D(self):
+        self.assertEqual(self.cos.run(1.13), math.cos(1.13))
+        
+    def test2D(self):
+        self.assertEqual(self.cos.run([1.13,0.56]), math.cos(1.13*math.cos(0.56))*math.cos(1.13*math.sin(0.56)))
+        self.assertEqual(self.cos.runXY([1.13,0.56]), math.cos(1.13)*math.cos(0.56))
+        
+class TestConstant(unittest.TestCase):
+    """Unit tests for Cos(x) function"""
+    
+    def setUp(self):
+        from sans.models.Constant import Constant
+        self.const = Constant()
+        self.const.setParam('value',56.1)
+
+    def test1D(self):
+        self.assertEqual(self.const.run(1.13), 56.1)
+        
+    def test2D(self):
+        self.assertEqual(self.const.run([1.13,0.56]), 56.1)
+        self.assertEqual(self.const.runXY([1.13,0.56]), 56.1)
+        
+    def testFunction(self):
+        from sans.models.Sin import Sin
+        s = Sin()
+        A = self.const
+        A.setParam('Value',1.5)
+        B = self.const.clone()
+        B.setParam('value',2.0)
+        C = self.const.clone()
+        C.setParam('value',3.0)
+        
+        f = A+B*s*s+C
+        answer = 1.5+2.0*math.sin(1.1)**2+3.0
+        self.assertEqual(f.run(1.1), answer)
+    
+        
+
+if __name__ == '__main__':
+    unittest.main()
Index: sansmodels/src/sans/models/test/validate_1D_model.py
===================================================================
--- sansmodels/src/sans/models/test/validate_1D_model.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/validate_1D_model.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,109 @@
+#!/usr/bin/env python
+""" 
+    Class to validate a given model by reading a test data set
+    generated from the IGOR SANS analysis tool from the NCNR
+"""
+import sys, math
+
+
+class Validate1D:
+    """ 
+        Class to validate a given model by reading a test data set
+        generated from the IGOR SANS analysis tool from the NCNR
+    """
+    
+    def __init__(self):
+        """ Initialization """
+        # Precision for the result comparison
+        self.precision = 0.0001
+        # Flag for end result
+        self.passed = True
+                
+    def __call__(self, filename):
+        """ 
+            Perform test on a data file
+            @param filename: name of the test data set
+        """
+        from sans.models.ModelFactory import ModelFactory
+        
+        # Read the data file
+        file_obj = open(filename,'r')
+        content = file_obj.read()
+        
+        # Flag to determine whether we are in the DATA section
+        started_data = False
+        # Model to test
+        model_object = None
+        
+        # Process each line of the file
+        for line in content.split('\n'):
+            if len(line)==0:
+                continue
+            try:
+                # Catch class name
+                if line.count("pythonclass")>0 and model_object==None:
+                    toks = line.split('=')
+                    print "Found class", toks[1]
+                    classname = toks[1].lstrip().rstrip()
+                    model_object = ModelFactory().getModel(classname)
+                
+                    # Output file for plotting
+                    file_out = open("%s_out.txt" % classname, 'w')
+                    file_out.write("<q>  <I_danse>  <I_igor>\n")
+                
+                # Process data
+                elif started_data:
+                    toks = line.split()
+                    q = float(toks[0])
+                    iq = float(toks[1])
+                    
+                    value = model_object.run(q)
+                    
+                    file_out.write("%g %g %g\n" % (q, value, iq))
+                    
+                    if math.fabs( (value - iq)/iq )>self.precision:
+                        self.passed = False
+                        print "ERROR q=%g: %g <> %g" % (q, model_object.run(q), iq)
+                        
+                # Catch DATA tag
+                elif line.count("DATA")>0:
+                    started_data = True
+                                
+                # Process parameters
+                elif started_data == False and not model_object==None:
+                    toks = line.split('=')
+                    if len(toks)==2:
+                        print "Setting parameter", line
+                        model_object.setParam(toks[0].lstrip().rstrip(), 
+                                              float(toks[1]))
+            except:
+                print "Could not parse line:\n %s" % line
+                print sys.exc_value
+            
+        file_obj.close()
+        file_out.close()
+        
+        print "Test passed = ", self.passed
+        return self.passed
+            
+if __name__ == '__main__':
+    validator = Validate1D()
+    all_pass = True
+    all_pass = all_pass and validator("sphere_testdata.txt")
+    print '\n'
+    all_pass = all_pass and validator("cylinder_testdata.txt")
+    print '\n'
+    all_pass = all_pass and validator("core_shell_cyl_testdata.txt")
+    print '\n'
+    all_pass = all_pass and validator("core_shell_testdata.txt")
+    print '\n'
+    all_pass = all_pass and validator("ellipsoid_testdata.txt")
+    print '\n'
+    all_pass = all_pass and validator("elliptical_cylinder_testdata.txt")
+
+    print '\nOverall result:', all_pass
+        
+        
+        
+  
+    
Index: sansmodels/src/sans/models/test/validate_2D_elliptical_cyl.py
===================================================================
--- sansmodels/src/sans/models/test/validate_2D_elliptical_cyl.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/validate_2D_elliptical_cyl.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,229 @@
+#!/usr/bin/env python
+""" 
+    Test for Elliptical Cylinder model.
+    Class to validate a given 2D model by averaging it
+    and comparing to 1D prediction.
+    
+"""
+import sys, math
+from sans.models.EllipticalCylinderModel import EllipticalCylinderModel
+
+
+class Validate2D:
+    """ 
+        Class to validate a given 2D model by averaging it
+        and comparing to 1D prediction.
+    """
+    
+    def __init__(self):
+        """ Initialization """
+        # Precision for the result comparison
+        self.precision = 0.000001
+        # Flag for end result
+        self.passed = True
+        # Verbose flag
+        self.verbose = True
+                
+    def __call__(self, npts = 101):
+        """ 
+            Perform test and produce output file
+            @param npts: number of points to average over
+            @return: True if the test passed, otherwise False
+        """
+        passed = True
+        
+        model = EllipticalCylinderModel()
+        
+        theta_label = 'cyl_theta'
+        if not model.params.has_key(theta_label):
+            theta_label = 'axis_theta'
+            
+        phi_label = 'cyl_phi'
+        if not model.params.has_key(phi_label):
+            phi_label = 'axis_phi'
+        
+        output_f = open("average_func.txt",'w')    
+        output_f.write("<q_average> <2d_average> <1d_average>\n")
+            
+        for i_q in range(1, 15):
+            q = 0.3/15.0*i_q
+            value = self.average_point_3D(model, q, npts)
+            ana = model.run(q)
+            if q<0.3 and (value-ana)/ana>0.05:
+                passed = False
+            output_f.write("%10g %10g %10g\n" % (q, value, ana))
+            if self.verbose:
+                print "Q=%g: %10g %10g %10g %10g" % (q, value, ana, value-ana, value/ana)
+        
+        output_f.close()
+        return passed
+    
+    def average_point_3D(self, model, q, npts):
+        """
+            Average intensity over all orientations
+            of the main cylinder axis and the 
+            rotation around that axis
+            
+            @param model: model to test
+            @param q: q-value
+            @param npts: number of points to average over
+            @return: average intensity
+        """
+        sum = 0.0
+        for i_theta in range(npts):
+            theta = math.pi/npts*i_theta
+            
+            model.setParam('cyl_theta', theta)
+            
+            for j in range(npts):
+                model.setParam('cyl_phi', math.pi * 2.0 / npts * j)
+                     
+                for k in range(npts):
+                    model.setParam("cyl_psi", math.pi * 2.0 / npts * k)
+                    
+                    if str(model.run([q, 0])).count("IN")>0:                        
+                        if self.verbose:
+                            #print "ERROR", q, theta, math.pi * 2.0 / npts * j
+                            pass
+                    else:
+                        sum += math.sin(theta)*model.run([q, 0])
+         
+        value = sum/npts/npts/npts
+        return value
+
+    def checkCylinder2D(self, phi):
+        """ 
+            Check that the 2D scattering intensity reduces
+            to a cylinder when r_ratio = 1.0
+            @param phi: angle of the vector q on the detector
+            @return: True if the test passed, otherwise False
+        """
+        from sans.models.CylinderModel import CylinderModel
+        
+        cyl = CylinderModel()
+        cyl.setParam("cyl_theta", 1.57)
+        cyl.setParam("cyl_phi", 0.0)
+        cyl.setParam("radius", 20)
+        cyl.setParam("length", 400)
+        cyl.setParam("contrast", 1.0e-6)
+
+        ell = EllipticalCylinderModel()
+        ell.setParam("r_ratio", 1.0)
+        ell.setParam("r_minor", 20)
+        ell.setParam("cyl_theta", 1.57)
+        ell.setParam("cyl_phi", 0.0)
+        ell.setParam("length", 400)
+        ell.setParam("contrast", 1.0e-6)
+        
+        passed = True
+        for i_q in range(1, 30):
+            q = 0.025*i_q
+            ell_val = ell.run([q, phi])
+            cyl_val = cyl.run([q, phi])
+            if self.verbose:
+                print "Q=%g    Ell=%g    Cyl=%g   R=%g" %(q, ell_val, cyl_val, ell_val/cyl_val)
+            if math.fabs(ell_val-cyl_val)/cyl_val>0.05:
+                passed= False
+                
+        return passed
+
+        
+    def checkCylinder(self, points):
+        """
+            Compare the average over all orientations
+            of the main cylinder axis for a cylinder
+            and the elliptical cylinder with r_ratio = 1
+            
+            @param points: number of points to average over
+            @return: True if the test passed, otherwise False
+        """
+        from sans.models.CylinderModel import CylinderModel
+        
+        passed = True
+        
+        npts =points
+        model = EllipticalCylinderModel()
+        model.setParam('r_ratio', 1.0)
+        model.setParam("r_minor", 20)
+        model.setParam("cyl_theta", 1.57)
+        model.setParam("cyl_phi", 0.0)
+        model.setParam("length", 400)
+        model.setParam("contrast", 1.0e-6)
+        
+        cyl = CylinderModel()
+        cyl.setParam("cyl_theta", 1.57)
+        cyl.setParam("cyl_phi", 0.0)
+        cyl.setParam("radius", 20)
+        cyl.setParam("length", 400)
+        cyl.setParam("contrast", 1.0e-6)
+        
+        output_f = open("average_func.txt",'w')    
+        output_f.write("<q_average> <2d_average> <1d_average>\n")
+            
+        for i_q in range(1, 15):
+            q = 0.3/15.0*i_q
+            value = self.average_point_2D(model, q, npts)
+            
+            ana = cyl.run(q)
+            if q<0.3 and math.fabs(value-ana)/ana>0.05:
+                passed = False
+            output_f.write("%10g %10g %10g\n" % (q, value, ana))
+            if self.verbose:
+                print "Q=%g: %10g %10g %10g %10g" % (q, value, ana, value-ana, value/ana)
+        
+        output_f.close()
+        return passed
+        
+        
+
+    def average_point_2D(self, model, q, npts):
+        """
+            Average intensity over all orientations
+            of the main cylinder axis
+            
+            @param model: model to test
+            @param q: q-value
+            @param npts: number of points to average over
+            @return: average intensity
+        """
+        sum = 0.0
+        
+        for i_theta in range(npts):
+            theta = math.pi/npts*i_theta            
+            model.setParam('cyl_theta', theta)
+            
+            for j in range(npts):
+                model.setParam('cyl_phi', math.pi * 2.0 / npts * j)
+                     
+                if str(model.run([q, 0])).count("IN")>0:                        
+                    if self.verbose:
+                        print "ERROR", q, theta, math.pi * 2.0 / npts * j
+                else:
+                    sum += math.sin(theta)*model.run([q, 0])
+        
+        value = sum/npts/npts*math.pi/2.0
+        return value
+
+    
+if __name__ == '__main__':
+    select = 0
+
+    validator = Validate2D()
+    validator.verbose = True
+    
+    print "Testing Elliptical cylinder to 5%\n"
+    
+    if select == 0 or select == 1:
+        # Check that the scat intensity reduces to a cylinder
+        # for r_ratio = 1
+        print "Comparing to Cyl I(q,theta):", validator.checkCylinder2D(1.5)
+    elif select == 0 or select == 2:
+        print "Comparing average to Cyl I(q):", validator.checkCylinder(100)
+    elif select == 0 or select == 3:
+        ellcyl_passed = validator(100)
+    
+        
+        
+        
+  
+    
Index: sansmodels/src/sans/models/test/validate_2D_model.py
===================================================================
--- sansmodels/src/sans/models/test/validate_2D_model.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
+++ sansmodels/src/sans/models/test/validate_2D_model.py	(revision ae3ce4eaa593202ebf49122e196897c0d45a25b6)
@@ -0,0 +1,206 @@
+#!/usr/bin/env python
+""" 
+    Class to validate a given 2D model by averaging it
+    and comparing to 1D prediction.
+    
+    The equation used for averaging is:
+    
+        (integral dphi from 0 to 2pi)(integral dtheta from 0 to pi)
+             p(theta, phi) I(q) sin(theta) dtheta
+             
+        = (1/N_phi) (1/N_theta) (pi/2) (sum over N_phi) (sum over N_theta)
+             p(theta_i, phi_i) I(q) sin(theta_i)
+             
+    where p(theta, phi) is the probability distribution normalized to 4pi.
+    In the current case, we put p(theta, phi) = 1.
+    
+    The normalization factor results from:
+          2pi/N_phi      for the phi sum
+        x pi/N_theta     for the theta sum
+        x 1/(4pi)        because p is normalized to 4pi
+        --------------
+        = (1/N_phi) (1/N_theta) (pi/2)
+    
+    Note: Ellipsoid
+        Averaging the 2D ellipsoid scattering intensity give a slightly
+        different output than the 1D function from the IGOR library 
+        at hight Q (Q>0.3). This is due to the way the IGOR library 
+        averages, taking only 76 points in alpha, the angle between
+        the axis of the ellipsoid and the q vector.
+        
+    Note: Core-shell and sphere models are symmetric around
+        all axes and don't need to be tested in the following way.
+"""
+import sys, math
+from sans.models.SphereModel import SphereModel
+from sans.models.CylinderModel import CylinderModel
+from sans.models.EllipsoidModel import EllipsoidModel
+from sans.models.CoreShellCylinderModel import CoreShellCylinderModel
+
+
+class Validate2D:
+    """ 
+        Class to validate a given 2D model by averaging it
+        and comparing to 1D prediction.
+    """
+    
+    def __init__(self):
+        """ Initialization """
+        # Precision for the result comparison
+        self.precision = 0.000001
+        # Flag for end result
+        self.passed = True
+                
+    def __call__(self, model_class=CylinderModel, points = 101):
+        """ 
+            Perform test and produce output file
+            @param model_class: python class of the model to test
+        """
+        print "Averaging %s" % model_class.__name__
+        passed = True
+        
+        npts =points
+        model = model_class()
+        #model.setParam('scale', 1.0)
+        #model.setParam('contrast', 1.0)
+        
+        theta_label = 'cyl_theta'
+        if not model.params.has_key(theta_label):
+            theta_label = 'axis_theta'
+            
+        phi_label = 'cyl_phi'
+        if not model.params.has_key(phi_label):
+            phi_label = 'axis_phi'
+        
+        output_f = open("%s_avg.txt" % model.__class__.__name__,'w')    
+        output_f.write("<q_average> <2d_average> <1d_average>\n")
+            
+        for i_q in range(1, 30):
+            q = 0.025*i_q
+            sum = 0.0
+            for i_theta in range(npts):
+                theta = math.pi/npts*i_theta
+                
+                model.setParam(theta_label, theta)
+                
+                for j in range(npts):
+                    model.setParam(phi_label, math.pi * 2.0 / npts * j)
+                    if str(model.run([q, 0])).count("INF")>0:                        
+                        print "ERROR", q, theta, math.pi * 2.0 / npts * j
+                    sum += math.sin(theta)*model.run([q, 0])
+                    #sum += model.run([q, 0])
+            
+            value = sum/npts/npts*math.pi/2.0
+            ana = model.run(q)
+            if q<0.3 and (value-ana)/ana>0.05:
+                passed = False
+            output_f.write("%10g %10g %10g\n" % (q, value, ana))
+            print "Q=%g: %10g %10g %10g %10g" % (q, value, ana, value-ana, value/ana)
+        
+        output_f.close()
+        return passed
+    
+    def average(self, model_class=CylinderModel, points = 101):
+        """ 
+            Perform test and produce output file
+            @param model_class: python class of the model to test
+        """
+        print "Averaging %s" % model_class.__name__
+        passed = True
+        
+        npts =points
+        model = model_class()
+        model.setParam('scale', 1.0)
+        model.setParam('contrast', 1.0)
+        
+        theta_label = 'cyl_theta'
+        if not model.params.has_key(theta_label):
+            theta_label = 'axis_theta'
+            
+        phi_label = 'cyl_phi'
+        if not model.params.has_key(phi_label):
+            phi_label = 'axis_phi'
+        
+        output_f = open("%s_avg.txt" % model.__class__.__name__,'w')    
+        output_f.write("<q_average> <2d_average> <1d_average>\n")
+            
+        for i_q in range(1, 30):
+            q = 0.025*i_q
+            sum = 0.0
+            theta = 1.57
+            
+            model.setParam(theta_label, theta)
+            
+            for j in range(npts):
+                model.setParam(phi_label, math.pi / 2.0 / npts * j)
+                if str(model.run([q, 0])).count("INF")>0:                        
+                    print "ERROR", q, theta, math.pi * 2.0 / npts * j
+                #sum += math.sin(theta)*model.run([q, 0])
+                sum += math.sin(math.pi / 2.0 / npts * j)*model.run([q, 0])
+                #sum += model.run([q, 0])
+            
+            value = sum/npts
+            ana = model.run(q)
+            if q<0.3 and (value-ana)/ana>0.05:
+                passed = False
+            output_f.write("%10g %10g %10g\n" % (q, value, ana))
+            print "Q=%g: %10g %10g %10g %10g" % (q, value, ana, value-ana, value/ana)
+        
+        output_f.close()
+        return passed
+        
+    def test_non_oriented(self, model_class=SphereModel, points = 101):
+        """ 
+            Perform test and produce output file
+            @param model_class: python class of the model to test
+        """
+        print "Averaging %s" % model_class.__name__
+        passed = True
+        
+        npts =points
+        model = model_class()
+        #model.setParam('scale', 1.0)
+        #model.setParam('contrast', 1.0)
+        
+        output_f = open("%s_avg.txt" % model.__class__.__name__,'w')    
+        output_f.write("<q_average> <2d_average> <1d_average>\n")
+            
+        for i_q in range(1, 30):
+            q = 0.025*i_q
+            sum = 0.0
+            for i_theta in range(npts):
+                theta = math.pi/npts*i_theta
+                
+                for j in range(npts):
+                    if str(model.run([q, 0])).count("INF")>0:                        
+                        print "ERROR", q, theta, math.pi * 2.0 / npts * j
+                    sum += math.sin(theta)*model.run([q, 0])
+            
+            value = sum/npts/npts*math.pi/2.0
+            ana = model.run(q)
+            if q<0.3 and (value-ana)/ana>0.05:
+                passed = False
+            output_f.write("%10g %10g %10g\n" % (q, value, ana))
+            print "Q=%g: %10g %10g %10g %10g" % (q, value, ana, value-ana, value/ana)
+        
+        output_f.close()
+        return passed
+            
+        
+if __name__ == '__main__':
+    validator = Validate2D()
+    cyl_passed = validator(CylinderModel, points=201)
+    ell_passed = validator(EllipsoidModel, points=501)
+    cylcorehsell_passed = validator(CoreShellCylinderModel, points=201) 
+    sph_passed = validator.test_non_oriented(SphereModel, points=211) 
+        
+    print ""
+    print "Model             Passed"
+    print "Cylinder          %s" % cyl_passed
+    print "Ellipsoid         %s" % ell_passed
+    print "Core-shell cyl    %s" % cylcorehsell_passed
+    print "Sphere            %s" % sph_passed
+        
+        
+  
+    
