Repository: mauriceling/copads (language: python)

Issue:
+ cleared up everything to restart



Discussion / hints:

Fix patch:
diff --git a/copads/__init__.py b/copads/__init__.py
index 9a53a3e..41b79c0 100644
--- a/copads/__init__.py
+++ b/copads/__init__.py
@@ -21,7 +21,7 @@
 Recipes which I found to be useful and can see how some of these 
 recipes can be merged. Thus, COPADS is borned.
 
-Project website: https://github.com/copads/copads
+Project website: https://github.com/mauriceling/copads
 
 License: Unless specified otherwise, all parts of this package, except
 those adapted, are covered under Python Software Foundation License
@@ -29,23 +29,14 @@
 """
 from datetime import datetime
 
-__version__ = '0.5.1'
+__version__ = '1.0.0'
 
-__author__ = 'Maurice H.T. Ling <mauriceling@acm.org>'
+__author__ = 'Maurice H.T. Ling <mauriceling@acm.org> on behalf of all developers'
 
 __copyright__ = '(c) 2007-%s, Maurice H.T. Ling.' % (datetime.now().year)
 
 # Data Structures
-from .bag import Bag
-from .dataframe import Dataframe
-from .dataframe import MultiDataframe
-from .dataframe import Series
-from .graph import Graph
-from .matrix import Matrix
-from .matrix import Vector
-from .parallelarray import ParallelArray
-from .prioritydictionary import PriorityDictionary
-from .ring import RingList
+
 
 # Operational classes
 from .randomize import MersenneTwister
@@ -74,20 +65,9 @@
 from .statisticsdistribution import WeiBullDistribution
 
 # Operational functions
-from . import entropy
-from . import hash
 from . import hypothesis
 from . import nrpy
 from . import objectdistance
 
 # Type-casting functions
-from .typecast import tc_Dataframe_Series
-from .typecast import tc_Dataframe_MultiDataframe
-from .typecast import tc_List_Dictionary
-from .typecast import tc_MultiDataframe_Dataframe
-from .typecast import tc_Series_Dataframe
-from .typecast import tc_Series_Dictionary
-from .typecast import tc_Series_List
-from .typecast import tc_Series_Vector
-from .typecast import tc_Vector_List
-from .typecast import tc_Vector_Dictionary
+
diff --git a/copads/constants.py b/copads/constants.py
new file mode 100644
index 0000000..e6d9aa7
--- /dev/null
+++ b/copads/constants.py
@@ -0,0 +1,181 @@
+"""
+List of Constant Numbers
+
+    - CGOLD: 2 - GOLD
+    - FRODA: Froda Constant
+    - GAMMA: Euler's gamma constant
+    - GOLD: golden mean, also known as golden ratio ((1 + sqrt(5))/2)
+    - GOLOMB = Golomb Constant
+    - INVLN10: inverse of natural logarithm of ten (1/ln(10))
+    - INVLN2: inverse of natural logarithm of 2 (1/ln(2))
+    - INVPI180: inverse of PI180, also means 180 divide by S{pi}
+    - INVSQRT2PI: inverse of SQRT2PI (1/(SQRT(2S{pi})))
+    - INV2PI: inverse of 2S{pi}
+    - LEHMER: Lehmer constant
+    - LN10: natural logarithm of 10
+    - LN2: natural logarithm of 2
+    - LNPI: natural logarithm of S{pi}
+    - PI: ratio of circumference of a circle to its diameter (S{pi})
+    - PI2: 2S{pi}
+    - PI180: S{pi} divided by 180
+    - PIDIV2: S{pi} divided by 2
+    - RABBIT: Rabbit Constant
+    - SQRT2: square root of 2
+    - SQRT2PI: square root of 2S{pi}
+    - ZETA9: S{sum}(1/n^9, n=1..infinity)
+    - CFRACT: S{pi}^2/(6 (ln 2) (ln 10))
+    - PARIS: Paris Constant
+    - LENGYEL: Lengyel Constant
+    - BACKHOUSE: Backhouse Constant
+    - PORTER: Porter Constant
+    
+Colour codes recognized by TK (TKColours): snow, ghost white, white smoke, gainsboro, floral white, old lace, linen, antique white, papaya whip, blanched almond, bisque, peach puff, navajo white, moccasin, cornsilk, ivory, lemon chiffon, seashell, honeydew, mint cream, azure, alice blue, lavender, lavender blush, misty rose, white, black, dark slate gray, dim gray, slate gray, light slate gray, gray, light grey, midnight blue, navy, cornflower blue, dark slate blue, slate blue, medium slate blue, light slate blue, medium blue, royal blue, blue, dodger blue, deep sky blue, sky blue, light sky blue, steel blue, light steel blue, light blue, powder blue, pale turquoise, dark turquoise, medium turquoise, turquoise, cyan, light cyan, cadet blue, medium aquamarine, aquamarine, dark green, dark olive green, dark sea green, sea green, medium sea green, light sea green, pale green, spring green, lawn green, green, chartreuse, medium spring green, green yellow, lime green, yellow green, forest green, olive drab, dark khaki, khaki, pale goldenrod, light goldenrod yellow, light yellow, yellow, gold, light goldenrod, goldenrod, dark goldenrod, rosy brown, indian red, saddle brown, sienna, peru, burlywood, beige, wheat, sandy brown, tan, chocolate, firebrick, brown, dark salmon, salmon, light salmon, orange, dark orange, coral, light coral, tomato, orange red, red, hot pink, deep pink, pink, light pink, pale violet red, maroon, medium violet red, violet red, magenta, violet, plum, orchid, medium orchid, dark orchid, dark violet, blue violet, purple, medium purple, thistle, snow2, snow3, snow4, seashell2, seashell3, seashell4, AntiqueWhite1, AntiqueWhite2, AntiqueWhite3, AntiqueWhite4, bisque2, bisque3, bisque4, PeachPuff2, PeachPuff3, PeachPuff4, NavajoWhite2, NavajoWhite3, NavajoWhite4, LemonChiffon2, LemonChiffon3, LemonChiffon4, cornsilk2, cornsilk3, cornsilk4, ivory2, ivory3, ivory4, honeydew2, honeydew3, honeydew4, LavenderBlush2, LavenderBlush3, LavenderBlush4, MistyRose2, MistyRose3, MistyRose4, azure2, azure3, azure4, SlateBlue1, SlateBlue2, SlateBlue3, SlateBlue4, RoyalBlue1, RoyalBlue2, RoyalBlue3, RoyalBlue4, blue2, blue4, DodgerBlue2, DodgerBlue3, DodgerBlue4, SteelBlue1, SteelBlue2, SteelBlue3, SteelBlue4, DeepSkyBlue2, DeepSkyBlue3, DeepSkyBlue4, SkyBlue1, SkyBlue2, SkyBlue3, SkyBlue4, LightSkyBlue1, LightSkyBlue2, LightSkyBlue3, LightSkyBlue4, SlateGray1, SlateGray2, SlateGray3, SlateGray4, LightSteelBlue1, LightSteelBlue2, LightSteelBlue3, LightSteelBlue4, LightBlue1, LightBlue2, LightBlue3, LightBlue4, LightCyan2, LightCyan3, LightCyan4, PaleTurquoise1, PaleTurquoise2, PaleTurquoise3, PaleTurquoise4, CadetBlue1, CadetBlue2, CadetBlue3, CadetBlue4, turquoise1, turquoise2, turquoise3, turquoise4, cyan2, cyan3, cyan4, DarkSlateGray1, DarkSlateGray2, DarkSlateGray3, DarkSlateGray4, aquamarine2, aquamarine4, DarkSeaGreen1, DarkSeaGreen2, DarkSeaGreen3, DarkSeaGreen4, SeaGreen1, SeaGreen2, SeaGreen3, PaleGreen1, PaleGreen2, PaleGreen3, PaleGreen4, SpringGreen2, SpringGreen3, SpringGreen4, green2, green3, green4, chartreuse2, chartreuse3, chartreuse4, OliveDrab1, OliveDrab2, OliveDrab4, DarkOliveGreen1, DarkOliveGreen2, DarkOliveGreen3, DarkOliveGreen4, khaki1, khaki2, khaki3, khaki4, LightGoldenrod1, LightGoldenrod2, LightGoldenrod3, LightGoldenrod4, LightYellow2, LightYellow3, LightYellow4, yellow2, yellow3, yellow4, gold2, gold3, gold4, goldenrod1, goldenrod2, goldenrod3, goldenrod4, DarkGoldenrod1, DarkGoldenrod2, DarkGoldenrod3, DarkGoldenrod4, RosyBrown1, RosyBrown2, RosyBrown3, RosyBrown4, IndianRed1, IndianRed2, IndianRed3, IndianRed4, sienna1, sienna2, sienna3, sienna4, burlywood1, burlywood2, burlywood3, burlywood4, wheat1, wheat2, wheat3, wheat4, tan1, tan2, tan4, chocolate1, chocolate2, chocolate3, firebrick1, firebrick2, firebrick3, firebrick4, brown1, brown2, brown3, brown4, salmon1, salmon2, salmon3, salmon4, LightSalmon2, LightSalmon3, LightSalmon4, orange2, orange3, orange4, DarkOrange1, DarkOrange2, DarkOrange3, DarkOrange4, coral1, coral2, coral3, coral4, tomato2, tomato3, tomato4, OrangeRed2, OrangeRed3, OrangeRed4, red2, red3, red4, DeepPink2, DeepPink3, DeepPink4, HotPink1, HotPink2, HotPink3, HotPink4, pink1, pink2, pink3, pink4, LightPink1, LightPink2, LightPink3, LightPink4, PaleVioletRed1, PaleVioletRed2, PaleVioletRed3, PaleVioletRed4, maroon1, maroon2, maroon3, maroon4, VioletRed1, VioletRed2, VioletRed3, VioletRed4, magenta2, magenta3, magenta4, orchid1, orchid2, orchid3, orchid4, plum1, plum2, plum3, plum4, MediumOrchid1, MediumOrchid2, MediumOrchid3, MediumOrchid4, DarkOrchid1, DarkOrchid2, DarkOrchid3, DarkOrchid4, purple1, purple2, purple3, purple4, MediumPurple1, MediumPurple2, MediumPurple3, MediumPurple4, thistle1, thistle2, thistle3, thistle4, gray1, gray2, gray3, gray4, gray5, gray6, gray7, gray8, gray9, gray10, gray11, gray12, gray13, gray14, gray15, gray16, gray17, gray18, gray19, gray20, gray21, gray22, gray23, gray24, gray25, gray26, gray27, gray28, gray29, gray30, gray31, gray32, gray33, gray34, gray35, gray36, gray37, gray38, gray39, gray40, gray42, gray43, gray44, gray45, gray46, gray47, gray48, gray49, gray50, gray51, gray52, gray53, gray54, gray55, gray56, gray57, gray58, gray59, gray60, gray61, gray62, gray63, gray64, gray65, gray66, gray67, gray68, gray69, gray70, gray71, gray72, gray73, gray74, gray75, gray76, gray77, gray78, gray79, gray80, gray81, gray82, gray83, gray84, gray85, gray86, gray87, gray88, gray89, gray90, gray91, gray92, gray93, gray94, gray95, gray97, gray98, gray99
+
+Copyright (c) Maurice H.T. Ling <mauriceling@acm.org>
+
+Date created: 19th March 2008
+"""
+
+CGOLD = 0.38196601125010515179
+FRODA = 6.580885991017920
+GAMMA = 0.57721566490153286060651209008240243104215933593992
+GOLD = 1.6180339887498948482045868343656381177203091798058
+GOLOMB = 0.6243299885435508
+INVLN10 = 0.43429448190325182765
+INVLN2 = 1.44269504088896340736
+INVPI180 = 57.2957795130823208768
+INVSQRT2PI = 0.39894228040143267794
+INV2PI = 0.1591549430918953420
+LEHMER = 0.5926327182916361
+LN10 = 2.30258509299404568402
+LN2 = 0.69314718055994530942
+LNPI = 1.14472988584940017414
+PI = 3.14159265358979323846
+PI2 = 6.2831853071795864769252867665590057683943387987502
+PI180 = 0.01745329251994329577
+RABBIT = 0.7098034428612913
+PIDIV2 = 1.57079632679489661923
+SQRT2 = 1.4142135623730950488016887242096980785696718753769
+SQRT2PI = 2.5066282746310005024157652848110452530069867406099
+ZETA9 = 1.002008392826082214417852769232412060485605851394888756548596615909785
+CFRACT = 1.03064083410071293588177609411693684092592031112072628177006095223495
+PARIS = 1.098641964394156485734668917343596210873348396108356550565441633848377
+LENGYEL = 1.09868580552518701
+BACKHOUSE = 1.4560749485826896713995953511165435576531783748471315402707024
+PORTER = 1.46707807943397547289779848470722995344990332241488777739968581761660
+
+TKColours = ['snow', 'ghost white', 'white smoke', 'gainsboro', 
+             'floral white', 'old lace', 'linen', 'antique white', 
+             'papaya whip', 'blanched almond', 'bisque', 'peach puff', 
+             'navajo white', 'moccasin', 'cornsilk', 'ivory', 
+             'lemon chiffon', 'seashell', 'honeydew', 'mint cream', 
+             'azure', 'alice blue', 'lavender', 'lavender blush', 
+             'misty rose', 'white', 'black', 'dark slate gray', 
+             'dim gray', 'slate gray', 'light slate gray', 'gray', 
+             'light grey', 'midnight blue', 'navy', 'cornflower blue', 
+             'dark slate blue', 'slate blue', 'medium slate blue', 
+             'light slate blue', 'medium blue', 'royal blue', 'blue', 
+             'dodger blue', 'deep sky blue', 'sky blue', 'light sky blue', 
+             'steel blue', 'light steel blue', 'light blue', 'powder blue', 
+             'pale turquoise', 'dark turquoise', 'medium turquoise', 
+             'turquoise', 'cyan', 'light cyan', 'cadet blue', 
+             'medium aquamarine', 'aquamarine', 'dark green', 
+             'dark olive green', 'dark sea green', 'sea green', 
+             'medium sea green', 'light sea green', 'pale green', 
+             'spring green', 'lawn green', 'green', 'chartreuse', 
+             'medium spring green', 'green yellow', 'lime green', 
+             'yellow green', 'forest green', 'olive drab', 'dark khaki', 
+             'khaki', 'pale goldenrod', 'light goldenrod yellow', 
+             'light yellow', 'yellow', 'gold', 'light goldenrod', 
+             'goldenrod', 'dark goldenrod', 'rosy brown', 
+             'indian red', 'saddle brown', 'sienna', 'peru', 
+             'burlywood', 'beige', 'wheat', 'sandy brown', 'tan', 
+             'chocolate', 'firebrick', 'brown', 'dark salmon', 'salmon', 
+             'light salmon', 'orange', 'dark orange', 'coral', 
+             'light coral', 'tomato', 'orange red', 'red', 'hot pink', 
+             'deep pink', 'pink', 'light pink', 'pale violet red', 
+             'maroon', 'medium violet red', 'violet red', 'magenta', 
+             'violet', 'plum', 'orchid', 'medium orchid', 'dark orchid', 
+             'dark violet', 'blue violet', 'purple', 'medium purple', 
+             'thistle', 'snow2', 'snow3', 'snow4', 'seashell2', 
+             'seashell3', 'seashell4', 'AntiqueWhite1', 'AntiqueWhite2', 
+             'AntiqueWhite3', 'AntiqueWhite4', 'bisque2', 'bisque3', 
+             'bisque4', 'PeachPuff2', 'PeachPuff3', 'PeachPuff4', 
+             'NavajoWhite2', 'NavajoWhite3', 'NavajoWhite4', 'LemonChiffon2', 
+             'LemonChiffon3', 'LemonChiffon4', 'cornsilk2', 'cornsilk3', 
+             'cornsilk4', 'ivory2', 'ivory3', 'ivory4', 'honeydew2', 
+             'honeydew3', 'honeydew4', 'LavenderBlush2', 'LavenderBlush3', 
+             'LavenderBlush4', 'MistyRose2', 'MistyRose3', 'MistyRose4', 
+             'azure2', 'azure3', 'azure4', 'SlateBlue1', 'SlateBlue2', 
+             'SlateBlue3', 'SlateBlue4', 'RoyalBlue1', 'RoyalBlue2', 
+             'RoyalBlue3', 'RoyalBlue4', 'blue2', 'blue4', 'DodgerBlue2', 
+             'DodgerBlue3', 'DodgerBlue4', 'SteelBlue1', 'SteelBlue2', 
+             'SteelBlue3', 'SteelBlue4', 'DeepSkyBlue2', 'DeepSkyBlue3', 
+             'DeepSkyBlue4', 'SkyBlue1', 'SkyBlue2', 'SkyBlue3', 'SkyBlue4', 
+             'LightSkyBlue1', 'LightSkyBlue2', 'LightSkyBlue3', 
+             'LightSkyBlue4', 'SlateGray1', 'SlateGray2', 'SlateGray3', 
+             'SlateGray4', 'LightSteelBlue1', 'LightSteelBlue2', 
+             'LightSteelBlue3', 'LightSteelBlue4', 'LightBlue1', 
+             'LightBlue2', 'LightBlue3', 'LightBlue4', 'LightCyan2', 
+             'LightCyan3', 'LightCyan4', 'PaleTurquoise1', 'PaleTurquoise2', 
+             'PaleTurquoise3', 'PaleTurquoise4', 'CadetBlue1', 'CadetBlue2', 
+             'CadetBlue3', 'CadetBlue4', 'turquoise1', 'turquoise2', 
+             'turquoise3', 'turquoise4', 'cyan2', 'cyan3', 'cyan4', 
+             'DarkSlateGray1', 'DarkSlateGray2', 'DarkSlateGray3', 
+             'DarkSlateGray4', 'aquamarine2', 'aquamarine4', 'DarkSeaGreen1', 
+             'DarkSeaGreen2', 'DarkSeaGreen3', 'DarkSeaGreen4', 'SeaGreen1', 
+             'SeaGreen2', 'SeaGreen3', 'PaleGreen1', 'PaleGreen2', 
+             'PaleGreen3', 'PaleGreen4', 'SpringGreen2', 'SpringGreen3', 
+             'SpringGreen4', 'green2', 'green3', 'green4', 'chartreuse2', 
+             'chartreuse3', 'chartreuse4', 'OliveDrab1', 'OliveDrab2', 
+             'OliveDrab4', 'DarkOliveGreen1', 'DarkOliveGreen2', 
+             'DarkOliveGreen3', 'DarkOliveGreen4', 'khaki1', 'khaki2', 
+             'khaki3', 'khaki4', 'LightGoldenrod1', 'LightGoldenrod2', 
+             'LightGoldenrod3', 'LightGoldenrod4', 'LightYellow2', 
+             'LightYellow3', 'LightYellow4', 'yellow2', 'yellow3', 
+             'yellow4', 'gold2', 'gold3', 'gold4', 'goldenrod1', 
+             'goldenrod2', 'goldenrod3', 'goldenrod4', 'DarkGoldenrod1', 
+             'DarkGoldenrod2', 'DarkGoldenrod3', 'DarkGoldenrod4', 
+             'RosyBrown1', 'RosyBrown2', 'RosyBrown3', 'RosyBrown4', 
+             'IndianRed1', 'IndianRed2', 'IndianRed3', 'IndianRed4', 
+             'sienna1', 'sienna2', 'sienna3', 'sienna4', 'burlywood1', 
+             'burlywood2', 'burlywood3', 'burlywood4', 'wheat1', 'wheat2', 
+             'wheat3', 'wheat4', 'tan1', 'tan2', 'tan4', 'chocolate1', 
+             'chocolate2', 'chocolate3', 'firebrick1', 'firebrick2', 
+             'firebrick3', 'firebrick4', 'brown1', 'brown2', 'brown3', 
+             'brown4', 'salmon1', 'salmon2', 'salmon3', 'salmon4', 
+             'LightSalmon2', 'LightSalmon3', 'LightSalmon4', 'orange2', 
+             'orange3', 'orange4', 'DarkOrange1', 'DarkOrange2', 
+             'DarkOrange3', 'DarkOrange4', 'coral1', 'coral2', 'coral3', 
+             'coral4', 'tomato2', 'tomato3', 'tomato4', 'OrangeRed2', 
+             'OrangeRed3', 'OrangeRed4', 'red2', 'red3', 'red4', 
+             'DeepPink2', 'DeepPink3', 'DeepPink4', 'HotPink1', 
+             'HotPink2', 'HotPink3', 'HotPink4', 'pink1', 'pink2', 
+             'pink3', 'pink4', 'LightPink1', 'LightPink2', 'LightPink3', 
+             'LightPink4', 'PaleVioletRed1', 'PaleVioletRed2', 
+             'PaleVioletRed3', 'PaleVioletRed4', 'maroon1', 'maroon2', 
+             'maroon3', 'maroon4', 'VioletRed1', 'VioletRed2', 'VioletRed3', 
+             'VioletRed4', 'magenta2', 'magenta3', 'magenta4', 'orchid1', 
+             'orchid2', 'orchid3', 'orchid4', 'plum1', 'plum2', 'plum3', 
+             'plum4', 'MediumOrchid1', 'MediumOrchid2', 'MediumOrchid3', 
+             'MediumOrchid4', 'DarkOrchid1', 'DarkOrchid2', 'DarkOrchid3', 
+             'DarkOrchid4', 'purple1', 'purple2', 'purple3', 'purple4', 
+             'MediumPurple1', 'MediumPurple2', 'MediumPurple3', 
+             'MediumPurple4', 'thistle1', 'thistle2', 'thistle3', 
+             'thistle4', 'gray1', 'gray2', 'gray3', 'gray4', 'gray5', 
+             'gray6', 'gray7', 'gray8', 'gray9', 'gray10', 'gray11', 
+             'gray12', 'gray13', 'gray14', 'gray15', 'gray16', 'gray17', 
+             'gray18', 'gray19', 'gray20', 'gray21', 'gray22', 'gray23', 
+             'gray24', 'gray25', 'gray26', 'gray27', 'gray28', 'gray29', 
+             'gray30', 'gray31', 'gray32', 'gray33', 'gray34', 'gray35', 
+             'gray36', 'gray37', 'gray38', 'gray39', 'gray40', 'gray42', 
+             'gray43', 'gray44', 'gray45', 'gray46', 'gray47', 'gray48', 
+             'gray49', 'gray50', 'gray51', 'gray52', 'gray53', 'gray54', 
+             'gray55', 'gray56', 'gray57', 'gray58', 'gray59', 'gray60', 
+             'gray61', 'gray62', 'gray63', 'gray64', 'gray65', 'gray66', 
+             'gray67', 'gray68', 'gray69', 'gray70', 'gray71', 'gray72', 
+             'gray73', 'gray74', 'gray75', 'gray76', 'gray77', 'gray78', 
+             'gray79', 'gray80', 'gray81', 'gray82', 'gray83', 'gray84', 
+             'gray85', 'gray86', 'gray87', 'gray88', 'gray89', 'gray90', 
+             'gray91', 'gray92', 'gray93', 'gray94', 'gray95', 'gray97', 
+             'gray98', 'gray99']
\ No newline at end of file
diff --git a/copads/copadsexceptions.py b/copads/copadsexceptions.py
new file mode 100644
index 0000000..66a2bf7
--- /dev/null
+++ b/copads/copadsexceptions.py
@@ -0,0 +1,213 @@
+'''!
+Exceptions Defined for COPADS. 
+
+    - CopadsError
+        - MatrixError
+        - GraphError
+            - EdgeNotFoundError
+            - VertexNotFoundError
+            - UnknownGraphMatrixError
+                - NotAdjacencyGraphMatrixError
+            - GraphEdgeSizeMismatchError
+        - StatisticsError
+            - DistributionError
+                - NormalDistributionTypeError
+            - DistributionParameterError
+            - DistributionFunctionError
+        - DistanceError
+            - DistanceInputSizeError
+        - TreeError
+            - TreeNodeTypeError
+        - FunctionParameterTypeError
+        - FunctionParameterValueError
+        - ParallelArrayError
+        - MaxIterationException
+        - VectorError
+
+Credits
+    - MatrixError subclasses 
+    (http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/189971)
+
+Copyright (c) Maurice H.T. Ling <mauriceling@acm.org>
+
+Date created: 1st May 2005
+'''
+
+class CopadsError(Exception):
+    '''!
+    Base class for all Copads-defined exceptions.
+    '''
+    pass
+        
+
+class MatrixError(CopadsError):
+    '''!
+    Exception to be thrown when encountered errors in Matrix onject.
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+
+    
+class GraphError(CopadsError):
+    '''!
+    Abstract parent for all graph exceptions
+    '''
+    pass
+
+class EdgeNotFoundError(GraphError):
+    '''!
+    Exception to be thrown when trying to retrieve an edge that is not 
+    found in the graph.
+    '''
+    def __init__(self, edge): self.edge = edge
+    def __str__(self): return "Edge, %s, is not found." % self.edge
+        
+class VertexNotFoundError(GraphError):
+    '''!
+    Exception to be thrown when trying to retrieve a vertex that is 
+    not found in the graph.
+    '''
+    def __init__(self, vertex): self.vertex = vertex
+    def __str__(self): return "Vertex, %s, is not found." % self.vertex
+        
+class UnknownGraphMatrixError(GraphError):
+    '''!
+    Exception to be thrown when trying to use an unknown type of graph 
+    matrix.
+    '''
+    def __init__(self, type): self.type = type
+    def __str__(self): return "Graph matrix type, %s, is not known." \
+        % self.type
+        
+class NotAdjacencyGraphMatrixError(UnknownGraphMatrixError):
+    '''!
+    Exception to be thrown when trying to enter a non-square matrix 
+    into Graph.GraphAdjacencyMatrix object or when not supplying an 
+    adjacency matrix when the calculation requires it.
+    '''
+    def __init__(self, type): self.type = type
+    def __str__(self):
+        return 'The graph inputted or required is not an adjacency matrix.'
+        
+class GraphEdgeSizeMismatchError(GraphError):
+    '''!
+    Exception for number of output edges do not match the number of 
+    input edges.
+    '''
+    def __init__(self, index, edge):
+        self.index = index
+        self.edge = edge
+    def __str__(self):
+        return "Number of edges do not match at index %s, %s" % \
+               (str(self.index), str(self.edge))
+        
+class GraphParameterError(GraphError):
+    '''!
+    Exception for parameter errors in Graph.Graph class.
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
+
+class StatisticsError(CopadsError):
+    '''!
+    Abstract parent for all statistics exceptions
+    '''
+    pass
+    
+class DistributionError(StatisticsError):
+    '''!
+    Abstract parent for all exceptions pertaining to statistical 
+    distributions
+    '''
+    pass
+    
+class NormalDistributionTypeError(DistributionError):
+    '''!
+    Exception for type errors in normal distribution 
+    (StatisticsDistribution.NormalDistribution).
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
+class DistributionParameterError(DistributionError):
+    '''!
+    Exception for parameter errors in distributions 
+    (StatisticsDistribution.*).
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
+class DistributionFunctionError(DistributionError):
+    '''!
+    Exception for undefined functions in distributions 
+    (StatisticsDistribution.*).
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
+class DistanceError(CopadsError):
+    '''!
+    Abstract parent for all exceptions related to calculating 
+    distances between lists.
+    '''
+    pass
+    
+class DistanceInputSizeError(DistanceError):
+    '''!
+    Exception for input parameter size errors for list (object) 
+    distance routines that have specific requirements for the size of 
+    inputs.
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
+class TreeError(CopadsError):
+    '''!
+    Abstract parent for all tree exceptions
+    '''
+    pass
+
+class TreeNodeTypeError(TreeError):
+    '''!
+    Exception to be thrown when trying to add a non-Node class object 
+    (Tree.Node) into a Tree object (Tree.BinaryTree)
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
+class FunctionParameterTypeError(CopadsError):
+    '''!
+    Exception to be thrown when trying a function parameter is of the 
+    wrong data type
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
+class FunctionParameterValueError(CopadsError):
+    '''!
+    Exception to be thrown when trying a function parameter is of 
+    wrong value
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
+class ParallelArrayError(CopadsError):
+    '''!
+    Exception to be thrown when encountered error in ParallelArray type'''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
+class MaxIterationsException(CopadsError):
+    '''!
+    Exception to catch maximum looping.
+    '''
+    pass
+    
+class VectorError(CopadsError):
+    '''!
+    Exception to be thrown when encountered errors in Vector onject.
+    '''
+    def __init__(self, msg): self.msg = msg
+    def __str__(self): return self.msg
+    
\ No newline at end of file
diff --git a/copads/genetic.py b/copads/genetic.py
new file mode 100644
index 0000000..48b2a2f
--- /dev/null
+++ b/copads/genetic.py
@@ -0,0 +1,763 @@
+"""
+Framework for Genetic Algorithm Applications.
+Date created: 23rd February 2010
+Licence: Python Software Foundation License version 2
+
+@see: Lim, JZR, Aw, ZQ, Goh, DJW, How, JA, Low, SXZ, Loo, BZL,
+Ling, MHT. 2010. A genetic algorithm framework grounded in biology.
+The Python Papers Source Codes 2: 6. 
+"""
+import random, os, string
+from copy import deepcopy
+
+class Chromosome(object):
+    """
+    Representation of a linear chromosome.
+
+    @see: Lim, JZR, Aw, ZQ, Goh, DJW, How, JA, Low, SXZ, Loo, BZL,
+    Ling, MHT. 2010. A genetic algorithm framework grounded in biology.
+    The Python Papers Source Codes 2: 6.
+    
+    @since: version 0.4
+    """
+    def __init__(self, sequence, base, 
+                 background_mutation=0.0001):
+        """
+        Sets up a chromosome.
+        
+        @param sequence: a subscriptable object (list or string) representing
+            the sequence of the chromosome.
+        @param base: a subscriptable object (list or string) representing
+            allowable entities in the sequence.
+        @param background_mutation: background mutation rate represented as the 
+            probability of number of mutations per base. Default = 0.0001 
+            (0.01%).
+            
+        @since: version 0.4
+        """
+        self.sequence = sequence
+        self.base = base
+        self.background_mutation = background_mutation
+    
+    def rmutate(self, type='point', rate=0.01, start=0, end=-1):
+        """
+        Random Mutation operator - to simulate random point, insertion, 
+        deletion, inversion, gene translocation and gene duplication events.
+        
+        The start and end parameters are useful for simulating mutational 
+        hotspots in the genome.
+        
+        @param type: type of mutation. Accepts 'point' (point mutation), 
+            'insert' (insert a base), 'delete' (delete a base), 'invert'
+            (invert a stretch of the chromosome), 'duplicate' (duplicate a
+            stretch of the chromosome), 'translocate' (translocate a stretch of
+            chromosome to another random position). Default = point.
+        @param rate: probability of mutation per base above background
+            mutation rate. Default = 0.01 (1%). No mutation event will ever 
+            happen if (rate + background_mutation) is less than zero.
+        @param start: starting base on the sequence for mutation.
+            Default = 0, start of the genome.
+        @param end: last base on the sequence for mutation. Default = -1, end of
+            the genome.
+            
+        @since: version 0.4
+        """
+        if end > len(self.sequence) - 1: end = len(self.sequence) - 1
+        if end == -1: end = len(self.sequence) - 1
+        if start == end: start = 0
+        length = int(end - start)
+        mutation = int((self.background_mutation + rate) * length)
+        while mutation > 0:
+            position = int(start) + random.randrange(length - 1)
+            new_base = self.base[random.randrange(len(self.base))]
+            if type == 'point': 
+                self.sequence[position] = new_base
+            if type == 'delete': 
+                self.sequence.pop(position)
+            if type == 'insert': 
+                self.sequence.insert(position, new_base)
+            if type == 'duplicate':
+                end_pos = random.randrange(position + 1, end)
+                fragment = self.sequence[position:end_pos]
+                for i in range(len(fragment)):
+                    self.sequence.insert(end_pos + i, fragment[i])
+            if type == 'invert':
+                end_pos = random.randrange(position + 1, end)
+                fragment = [self.sequence.pop(position) 
+                            for i in range(end_pos - position)]
+                fragment.reverse()
+                for base in fragment:
+                    self.sequence.insert(position, base)
+            if type == 'translocate':
+                end_pos = random.randrange(position + 1, end)
+                fragment = [self.sequence.pop(position) 
+                            for i in range(end_pos - position)]
+                insertion_point = random.randint(0, len(self.sequence))
+                for i in range(len(fragment)):
+                    self.sequence.insert(insertion_point + i, fragment[i])
+            mutation = mutation - 1
+    
+    def kmutate(self, type='point', start=0, end=0, sequence=None, tpos=0):
+        """
+        Known Mutation operator - to simulate a known point, insertion, 
+        deletion, inversion, gene translocation or gene duplication event.
+        
+        The required parameters will be determined by the type of mutation:
+            - point requires 
+                - start (the position of the base to mutate)
+                - sequence (the new base)
+            - delete requires
+                - start (the position of the base to delete)
+            - insert requires
+                - start (the position of the base to begin insertion)
+                - sequence (list of sequence or a base to insert)
+            - invert requires
+                - start (the position of the base to start inversion)
+                - end (the position of the base to end inversion)
+            - duplicate requires
+                - start (the position of the starting base to duplicate)
+                - end (the position of the last base to duplicate)
+            - translocate requires
+                - start (the position of the base to start translocation)
+                - end (the position of the base to end translocation)
+                - tpos (the position of the base insert the translocated 
+                    sequence)
+                
+        @param type: type of mutation. Accepts 'point' (point mutation), 
+            'insert' (insert a base), 'delete' (delete a base), 'invert'
+            (invert a stretch of the chromosome), 'duplicate' (duplicate a
+            stretch of the chromosome), 'translocate' (translocate a stretch of
+            chromosome to another random position). Default = point.
+        @param start: starting base on the chromosome for mutation.
+            Default = 0, start of the genome.
+        @param end: last base on the chromosome for mutation. Default = 0.
+        @param sequence: list of bases to change to (point mutation) or to 
+            insert (insertion mutation)
+        @param tpos: position of the chromosome to insert the translocated
+            sequence.
+            
+        @since: version 0.4
+        """
+        if type == 'point':
+            self.sequence[start] = sequence
+        if type == 'delete': 
+            self.sequence.pop(start)
+        if type == 'insert':
+            for base in list(sequence):
+                self.sequence.insert(start, base)
+        if type == 'duplicate':
+            fragment = self.sequence[start:end + 1]
+            for i in range(len(fragment)):
+                self.sequence.insert(end + i, fragment[i])
+        if type == 'invert':
+            for base in self.sequence[start:end]:
+                self.sequence.insert(start, base)
+        if type == 'translocate':
+            fragment = [self.sequence.pop(start) 
+                        for i in range(end - start)]
+            for i in range(len(fragment)):
+                self.sequence.insert(tpos + i, fragment[i])
+
+    def replicate(self):
+        """
+        Replicates (deep copy) the chromosome.
+        
+        @return: a copy of current chromosome.
+        
+        @since: version 0.4
+        """
+        return deepcopy(self)
+ 
+        
+class Organism(object):
+    """
+    An organism represented by a list of chromosomes and a status table. This 
+    class should almost never be instantiated on its own but acts as an ancestor
+    class as some functions need to be over-ridden in the inherited class for 
+    the desired use. 
+    
+    Each organism is identifiable by a randomly generated 32-character 'name' as
+    Organism.identity.
+    
+    Methods to be over-ridden in the inherited class or substituted are
+        - fitness
+        - mutation_scheme
+    
+    Pre-defined status are
+        1. alive - is the organism alive? True or False.
+        2. vitality - percentage of maximum vitality.
+        3. age - the current age of the organism.
+        4. lifespan - pre-defined maximum lifespan to be set based on scenario. 
+        5. fitness - how fit the organism is? Set to maximum fitness.
+        6. death - reason of death (as death code).
+    
+    List of defined death codes
+        1. death01 - zero vitality
+        2. death02 - maximum age reached
+        3. death03 - unknown death cause
+
+    @see: Lim, JZR, Aw, ZQ, Goh, DJW, How, JA, Low, SXZ, Loo, BZL,
+    Ling, MHT. 2010. A genetic algorithm framework grounded in biology.
+    The Python Papers Source Codes 2: 6.
+    
+    @since: version 0.4
+    """
+    status = {'alive': True,                # is the organism alive?
+              'vitality': 100.0,            # % of vitality
+              'age': 0.0,                   # age of the organism
+              'lifespan': 100.0,            # maximum lifespan
+              'fitness': 100.0,             # % of fitness
+              'death': None}
+              
+    genome = []
+    
+    def __init__(self, genome='default',
+                 mutation_type='point',
+                 additional_mutation_rate=0.01, gender=None):
+        """
+        Sets up a new organism with default status (age = 0, vitality = 100,
+        lifespan = 100, fitness = 100, alive = True)
+        
+        @param genome: list of chromosomes to inherit. Default = 'default', 
+            which will set up one default chromosome. It also allows a 
+            'dummy' chromosome which is basically a one-base chromosome - this 
+            is for applications which does not utilize the chromosome.
+        @param mutation_type: type of mutation. Accepts 'point' (point 
+            mutation), 'insert' (insert a base), 'delete' (delete a base), 
+            'invert' (invert a stretch of the chromosome), 'duplicate' 
+            (duplicate a stretch of the chromosome), 'translocate' (translocate 
+            a stretch of chromosome to another random position). 
+            Default = point.
+        @param additional_mutation_rate: probability of mutation per base above 
+            background mutation rate. Default = 0.01 (1%). No mutation event 
+            will ever happen if (rate + background_mutation) is less than zero.
+        @param gender: establishes the gender of the organism which may be used
+            for mating routines.
+            
+        @since: version 0.4
+        """
+        if genome == 'default': 
+            self.genome = [Chromosome()]
+        elif genome == 'dummy':
+            self.genome = [Chromosome([0])]
+        else: 
+            self.genome = genome
+        self.mutation_type = mutation_type
+        self.additional_mutation_rate = additional_mutation_rate
+        self.gender = gender
+    
+    def fitness(self):
+        """
+        Function to calculate the fitness of the current organism. 
+        B{This function MUST be over-ridden by the inherited class or 
+        substituted as fitness function is highly dependent on utility.} The 
+        only requirement is that a fitness score must be returned.
+        
+        Here, the sample implementation calculates fitness as proportion of the
+        genome with '1's.
+        
+        @return: fitness score or fitness list
+        
+        @since: version 0.4
+        """
+        one_count = sum([sum(chromosome.sequence) 
+                         for chromosome in self.genome])
+        length_of_genome = sum([len(chromosome.sequence) 
+                                for chromosome in self.genome])
+        return float(one_count) / float(length_of_genome)
+    
+    def mutation_scheme(self, type=None, rate=None):
+        """
+        Function to trigger mutation events in each chromosome. B{This function
+        may be over-ridden by the inherited class or substituted to cater for 
+        specific mutation schemes but not an absolute requirement to do so.}
+        
+        Both type and rate must be defined at the same time, otherwise the 
+        initiated mutation_type and additional_mutation_rate will be used.
+        
+        @param type: type of mutation. Accepts 'point' (point mutation), 
+            'insert' (insert a base), 'delete' (delete a base), 'invert' 
+            (invert a stretch of the chromosome), 'duplicate' (duplicate a 
+            stretch of the chromosome), 'translocate' (translocate a stretch of 
+            chromosome to another random position). Default = None.
+        @param rate: probability of mutation per base above background mutation 
+            rate. Default = None. No mutation event will ever happen if 
+            (rate + background_mutation) is less than zero.
+            
+        @since: version 0.4
+        """
+        for chromosome in self.genome: 
+            if not type and not rate:
+                chromosome.rmutate(self.mutation_type, 
+                                   self.additional_mutation_rate)
+            if type and rate:
+                chromosome.rmutate(type, rate)
+        
+    def setStatus(self, variable, value):
+        """
+        Sets new status or change status of the organism. However, the following
+        status change will result in death of the organism
+            1. 'alive' to False
+            2. 'vitality' to or below zero
+            3. 'age' to or above lifespan
+        
+        @param variable: name of status to change
+        @param value: new value of the status
+        
+        @since: version 0.4
+        """
+        if variable == 'alive' and value == True: 
+            self.status['alive'] = value
+        if variable == 'alive' and value == False: 
+            self.status['alive'] = value
+            self.status['death'] = 'death03'
+        if variable == 'vitality':
+            if float(value) > 100.1:
+                self.status['vitality'] = 100.0
+            if float(value) > 0.0 and float(value) < 100.1:
+                self.status['vitality'] = float(value)
+            if float(value) == 0 or float(value) < 0:
+                self.status['vitality'] = 0
+                self.status['alive'] = False
+                self.status['death'] = 'death01'
+        elif variable == 'age':
+            if float(value) < self.status['lifespan']:
+                self.status['age'] = float(value)
+            else:
+                self.status['age'] = self.status['lifespan']
+                self.status['alive'] = False
+                self.status['death'] = 'death02'
+        else:
+            self.status[variable] = value
+        
+    def getStatus(self, variable):
+        """
+        Returns a status variable of the current organism
+        
+        @param variable: name of the status variable
+        @return: status or a KeyError if status is not found
+        
+        @since: version 0.4
+        """
+        try:
+            return self.status[variable]
+        except:
+            raise KeyError('%s not found in organism status' % str(variable))
+    
+    def __str__(self):
+        """Returns the genome of the organism"""
+        return str([chromosome.sequence for chromosome in self.genome])
+        
+    def clone(self):
+        """
+        Cloness (deep copy) the organism.
+        
+        @return: a copy of current organism.
+        
+        @since: version 0.4
+        """
+        return deepcopy(self)
+        
+        
+class Population(object):
+    """
+    Representation of a population as a list of organisms. The entire 
+    population is in memory.
+    
+    Methods to be over-ridden in the inherited class or substituted are
+        - prepopulation_control
+        - mating
+        - postpopulation_control
+        - generation_events
+        - report
+
+    @see: Lim, JZR, Aw, ZQ, Goh, DJW, How, JA, Low, SXZ, Loo, BZL,
+    Ling, MHT. 2010. A genetic algorithm framework grounded in biology.
+    The Python Papers Source Codes 2: 6.
+    
+    @since: version 0.4
+    """
+    
+    def __init__(self, goal, maxgenerations='infinite', agents=[]):
+        """
+        Establishes a population of organisms.
+        
+        @param goal: the goal to be reached by the population.
+        @type goal: the return type of Organism.fitness()
+        @param maxgenerations: maximum number of generations to evolve.
+            Default = 'infinite'.
+        @param agents: organisms making up the initial population.
+        @type agents: list of Organism objects
+        
+        @since: version 0.4
+        """
+        self.agents = agents
+        self.goal = goal
+        self.maxgenerations = maxgenerations
+        self.generation = 0
+    
+    def prepopulation_control(self):
+        """
+        Function to trigger population control events before mating event in 
+        each generation (For example, to simulate pre-puberty death). B{This 
+        function may be over-ridden by the inherited class or substituted to 
+        cater for specific events.} Although this is not an absolute 
+        requirement, it is extremely encouraged to prevent exhaustion of memory 
+        space. Without population control, it will seems like a reproducing 
+        immortal population.
+        
+        Here, the sample implementation eliminates the bottom half of the 
+        population based on fitness score unless the number of organisms is
+        less than 20. This is to prevent extinction. However, if the number of
+        organisms is more than 2000 (more than 100x initial population size, 
+        a random selection of 2000 will be used for the next generation.
+        
+        @since: version 0.4
+        """
+        size = len(self.agents)
+        sfitness = [self.agents[x].fitness() for x in range(size)]
+        threshold = sum(sfitness) / len(sfitness)
+        temp = [self.agents[x]
+                for x in range(size) 
+                    if sfitness[x] > threshold]
+        if len(temp) > 2001:
+            size = len(temp)
+            self.agents = [temp[random.randint(0, size - 1)] 
+                           for x in range(2000)]
+        if len(temp) > 21: 
+            self.agents = temp
+    
+    def mating(self):
+        """
+        Function to trigger mating events in each generation. B{This function
+        may be over-ridden by the inherited class or substituted to cater for 
+        specific mating schemes but not an absolute requirement to do so.} 
+        Mating schemes should include 
+            - selection of mating partners using Organism.fitness() function
+                and/or other status
+            - processes and actions of mating
+            
+        Here, the sample implementation randomly selects any 2 organisms from
+        the culled list and generate a new genome for the progeny organism by
+        single crossover.
+        
+        @since: version 0.4
+        """
+        size = len(self.agents)
+        temp = []
+        for x in range(size):
+            organism1 = self.agents[random.randint(0, size - 1)]
+            organism2 = self.agents[random.randint(0, size - 1)]
+            crossover_pt = random.randint(0, len(organism1.genome[0].sequence))
+            (g1, g2) = crossover(organism1.genome[0], organism2.genome[0],
+                                 crossover_pt)
+            temp = temp + [Organism([g1])]
+        self.add_organism(temp)
+            
+    def postpopulation_control(self):
+        """
+        Function to trigger population control events after mating event in 
+        each generation(For example, to simulate old-age death). B{This 
+        function may be over-ridden by the inherited class or substituted to 
+        cater for specific events.} Although this is not an absolute 
+        requirement, it is extremely encouraged to prevent exhaustion of memory 
+        space. Without population control, it will seems like a reproducing 
+        immortal population.
+        
+        @since: version 0.4
+        """
+        pass
+        
+    def generation_events(self):
+        """
+        Function to trigger other defined events in each generation. B{This 
+        function may be over-ridden by the inherited class or substituted to 
+        cater for specific events but not an absolute requirement to do so.} 
+        Events and controls may include
+            - processes simulating disaster or other catastrophic events
+            - changes in mutations 
+            
+        @since: version 0.4           
+        """
+        pass
+    
+    def report(self):
+        """
+        Function to report the status of each generation. B{This function may 
+        be over-ridden by the inherited class or substituted to cater for 
+        specific reporting schemes but not an absolute requirement to do so.} 
+        At the very least, this function should report whether the goal is 
+        reached.
+        
+        @return: dictionary of status describing the current generation
+        
+        @since: version 0.4
+        """
+        sfitness = [self.agents[x].fitness() for x in range(len(self.agents))]
+        afitness = sum(sfitness) / float(len(self.agents))
+        return {'generation': self.generation,
+                'average fitness': afitness,
+                '% to goal': float(afitness - self.goal) / self.goal * 100}
+        
+    def generation_step(self):
+        """
+        Function to simulate events for one generation. These includes
+            - mating (according to the mating scheme or function)
+            - mutating each organism in the population
+            - population size control
+            - other events defined under generation_events function
+            - increment of generation count
+            - reporting the population status
+        
+        @return: information returned from report function.
+        
+        @since: version 0.4
+        """
+        if self.generation > 0:
+            self.prepopulation_control()
+        self.mating()
+        self.postpopulation_control()
+        for organism in self.agents:
+            organism.mutation_scheme() 
+        self.generation_events()
+        self.generation = self.generation + 1
+        return self.report()
+    
+    def add_organism(self, organism):
+        """Add a new organism(s) to the population.
+        
+        @param organism: list of new Organism object(s)
+        
+        @since: version 0.4"""
+        self.agents = self.agents + organism
+        
+    def freeze(self, prefix='pop', proportion=0.01):
+        """
+        Preserves part or the entire population. If the population size or the
+        preserved proportion is below 100, the entire population will be 
+        preserved. The preserved sample will be written into a file with name in
+        the following format - <prefix><generation count>_<sample size>.gap
+        
+        @param prefix: prefix of file name. Default = 'pop'.
+        @param proportion: proportion of population to be preserved.
+            Default = 0.01, preserves 1% of the population.
+            
+        @since: version 0.4
+        """
+        import pickle
+        if proportion > 1.0: proportion = 1.0
+        if len(self.agents) < 101 or len(self.agents) * proportion < 101:
+            sample = self.agents
+        else:
+            size = len(self.agents)
+            sample = [self.agents[random.randint(0, size - 1)]
+                      for x in range(int(len(self.agents) * proportion))]
+        name = ''.join([prefix, str(self.generation), '_', 
+                        str(len(sample)), '.gap'])
+        f = open(name, 'wb')
+        pickle.dump(sample, f)
+        f.close()
+        
+    def revive(self, filename, type='replace'):
+        """
+        Revives a frozen population.
+        
+        @param filename: file name of frozen population (generated by 
+            Population.freeze() function)
+        @param type: type of revival. Allows 'replace' (replace the current 
+            population with the revived population) or 'add' (add the revived
+            population to the current population). Default = 'replace'.
+            
+        @since: version 0.4
+        """
+        import pickle
+        if type == 'replace':
+            self.agents = pickle.load(open(filename, 'rb'))
+        if type == 'add':
+            self.agents = self.agents + pickle.load(open(filename, 'rb'))
+
+def crossover(chromosome1, chromosome2, position):
+    """
+    Cross-over operator - swaps the data on the 2 given chromosomes after 
+    a given position.
+    
+    @param chromosome1: Chromosome object
+    @param chromosome2: Chromosome object
+    @param position: base position of the swap over
+    @type position: integer
+    @return: (resulting chromosome1, resulting chromosome2)
+    
+    @since: version 0.4
+    """
+    seq1 = chromosome1.sequence
+    seq2 = chromosome2.sequence 
+    position = int(position)
+    if len(seq1) > position and len(seq2) > position:
+        new1 = Chromosome(seq1[:position] + seq2[position:], 
+                          chromosome1.base)
+        new2 = Chromosome(seq2[:position] + seq1[position:],
+                          chromosome2.base)
+        return (new1, new2)
+    elif len(seq1) > position:
+        new1 = Chromosome(seq1[:position], chromosome1.base)
+        new2 = Chromosome(chromosome2.sequence + seq1[position:],
+                          chromosome2.base)
+        return (new1, new2)
+    elif len(seq2) > position:
+        new1 = Chromosome(chromosome1.sequence + seq2[position:],
+                          chromosome1.base)
+        new2= Chromosome(seq2[:position], chromosome2.base)
+        return (new1, new2)
+    else:
+        return (chromosome1, chromosome2)        
+            
+population_data = \
+{
+    'nucleotide_list' : [1, 2, 3, 4],
+    'chromosome_length' : 200,
+    'chromosome_type' : 'defined',
+    'chromosome' : [1] * 200,
+    'background_mutation' : 0.0001,
+    'genome_size' : 1,
+    'population_size' : 200,
+    'fitness_function' : 'default',
+    'mutation_scheme' : 'default',
+    'additional_mutation_rate' : 0.01,
+    'mutation_type' : 'point',
+    'goal' : 4,
+    'maximum_generation' : 'infinite',
+    'prepopulation_control' : 'default',
+    'mating' : 'default',
+    'postpopulation_control' : 'default',
+    'generation_events' : 'default',
+    'report' : 'default'
+}
+
+def population_constructor(data=population_data):
+    """
+    Function to construct a population based on a dictionary of population data.
+    
+    Population data contains the following keys:
+        - 'nucleotide_list' = List of allowable nucleotides (bases). 
+            Default = [1, 2, 3, 4].
+        - 'chromosome_length' = Length of a chromosome. Default = 200.
+        - 'chromosome_type' = Type of chromosome. Default = 'defined'.
+        - 'chromosome' = Initial chromosome. Default = [1] * 200.
+        - 'background_mutation' = Background mutation rate. 
+            Default = 0.0001 (0.01%).
+        - 'genome_size' = Number of chromosomes per organism. Default = 1.
+        - 'population_size' = Size of initial population (number of organisms).
+            Default = 200.
+        - 'fitness_function' = Fitness evaluation function. Accepts 'default' 
+            or a function. Please refer to Organism. Default = 'default'.
+        - 'mutation_scheme' = Function simulating the mutation scheme of an 
+            organism. Accepts 'default' or a function. Please refer to Organism. 
+            Default = 'default'.
+        - 'additional_mutation_rate' = Mutation rate on top of background 
+            mutation rate. Default = 0.01 (1%).
+        - 'mutation_type' = Type of default mutation. Default = 'point'.
+        - 'goal' = Goal of the population, as evaluated by fitness function.
+            Default = 4.
+        - 'maximum_generation' = Number of generations to simulate. Accepts an 
+            integer or 'infinite'. Default = 'infinite'.
+        - 'prepopulation_control' = Function simulating pre-mating population 
+            control. Please refer to Population. Default = 'default'.
+        - 'mating' = Function simulating mating procedure (mate selection and 
+            act of mating control. Please refer to Population. 
+            Default = 'default'.
+        - 'postpopulation_control' = Function simulating post-mating population 
+            control. Please refer to Population. Default = 'default'.
+        - 'generation_events' = Function simulating other possible (usually 
+            rare or random) events in the generation. Please refer to 
+            Population. Default = 'default'.
+        - 'report' = Function to generate the status report of the generation.
+            Please refer to Population. Default = 'default'.
+        
+    @param data: population data
+    @type data: dictionary
+        
+    @return: Population object
+
+    @see: Lim, JZR, Aw, ZQ, Goh, DJW, How, JA, Low, SXZ, Loo, BZL,
+    Ling, MHT. 2010. A genetic algorithm framework grounded in biology.
+    The Python Papers Source Codes 2: 6.
+    
+    @since: version 0.4
+    """
+    chr = Chromosome(data['chromosome'], 
+                     data['nucleotide_list'],
+                     data['background_mutation'])
+    org = Organism([chr]*data['genome_size'],
+                   data['mutation_type'],
+                   data['additional_mutation_rate'])
+    if data['fitness_function'] != 'default':
+        Organism.fitness = data['fitness_function']
+    if data['mutation_scheme'] != 'default':
+        Organism.mutation_scheme = data['mutation_scheme']
+    org_set = [org.clone() for x in range(data['population_size'])]
+    pop = Population(data['goal'], 
+                     int(data['maximum_generation']), 
+                     org_set)
+    if data['prepopulation_control'] != 'default':
+        Population.prepopulation_control = data['prepopulation_control']
+    if data['mating'] != 'default':
+        Population.mating = data['mating']
+    if data['postpopulation_control'] != 'default':
+        Population.postpopulation_control = data['postpopulation_control']
+    if data['generation_events'] != 'default':
+        Population.generation_events = data['generation_events']
+    if data['report'] != 'default':
+        Population.report = data['report']
+    return pop
+    
+def population_simulate(population, 
+                        printfreq=100, 
+                        freezefreq='never', 
+                        freezefile='pop',
+                        freezeproportion=0.01, 
+                        resultfile='result.txt'):
+    """
+    Function to simulate the population - start the GA.
+    
+    @param population: Population to run.
+    @type population: Population object
+    @param printfreq: Reporting intervals on screen. Default = 100.
+    @param freezefreq: Generation intervals to freeze population into a file.
+        See Population.freeze method. Accepts an integer or 'never'. 
+        Default = 'never'.
+    @param freezefile: Prefix of file name of frozen population. 
+        See Population.freeze method. Default = 'pop'.
+    @param freezeproportion: Proportion of population to be preserved.
+        See Population.freeze method. Default = 0.01, preserves 1% of the 
+        population.
+    @param resultfile: Name of file to print out results of each generation. 
+        Format of output is dependent on reporting method of the population
+        (Population.report). If 'None', it will print out the results into
+        a file. Default = 'result.txt'.
+
+    @see: Lim, JZR, Aw, ZQ, Goh, DJW, How, JA, Low, SXZ, Loo, BZL,
+    Ling, MHT. 2010. A genetic algorithm framework grounded in biology.
+    The Python Papers Source Codes 2: 6.
+    
+    @since: version 0.4
+    """
+    if freezefreq == 'never': freezefreq = int(12e14)
+    if resultfile != 'None': result = open(resultfile, 'w')
+    report = population.generation_step()
+    reportitems = ['|'.join([str(key), str(report[key])])
+                    for key in list(report.keys())]
+    result.writelines('|'.join(reportitems))
+    result.writelines(os.linesep)
+    while population.generation < population.maxgenerations:
+        if resultfile != 'None': 
+            report = population.generation_step()
+            reportitems = ['|'.join([str(key), str(report[key])])
+                           for key in list(report.keys())]
+            result.writelines('|'.join(reportitems))
+        if population.generation % int(printfreq) == 0:
+            print('|'.join(reportitems))
+        result.writelines(os.linesep)
+        if population.generation % int(freezefreq) == 0:
+            population.freeze(freezefile, freezeproportion)
+    population.freeze(freezefile, 1.0)
+    result.close()
diff --git a/copads/hypothesis.py b/copads/hypothesis.py
new file mode 100644
index 0000000..f2d5c0e
--- /dev/null
+++ b/copads/hypothesis.py
@@ -0,0 +1,1406 @@
+"""
+Statistical Hypothesis Testing Routines.
+
+Each routine will Returns a 5-element list
+    - [left result, left critical, statistic, right critical, right result]
+where
+    - left result = True (statistic in lower critical region) or
+    False (statistic not in lower critical region)
+    - left critical = lower critical value generated from 1 - confidence
+    - statistic = calculated statistic value
+    - right critical = upper critical value generated from confidence
+    - right result = True (statistic in upper critical region) or
+    False (statistic not in upper critical region)
+
+References
+    - Test 1-100: Gopal K. Kanji. 2006. 100 Statistical Tests, 3rd edition.
+    Sage Publications.
+
+Copyright (c) Maurice H.T. Ling <mauriceling@acm.org>
+
+Date created: 1st September 2008
+"""
+
+from math import sqrt, log, e
+from statisticsdistribution import *
+
+def test(statistic, distribution, confidence):
+    """Generates the critical value from distribution and confidence value
+    using the distribution's inverseCDF method and performs 1-tailed and
+    2-tailed test by comparing the calculated statistic with the critical
+    value.
+
+
+    Returns a 5-element list
+    [left result, left critical, statistic, right critical, right result]
+    where
+        - left result = True (statistic in lower critical region) or
+        False (statistic not in lower critical region)
+        - left critical = lower critical value generated from 1 - confidence
+        - statistic = calculated statistic value
+        - right critical = upper critical value generated from confidence
+        - right result = True (statistic in upper critical region) or
+        False (statistic not in upper critical region)
+
+    Therefore, null hypothesis is accepted if left result and right result are
+    both False in a 2-tailed test.
+
+    @param statistic: calculated statistic (float)
+    @param distribution: distribution to calculate critical value
+    @type distribution: instance of a statistics distribution
+    @param confidence: confidence level of a one-tail
+        test (usually 0.95 or 0.99), use 0.975 or 0.995 for 2-tail test
+    @type confidence: float of less than 1.0"""
+    data = [None, None, statistic, None, None]
+    data[1] = distribution.inverseCDF(1.0 - confidence)[0]
+    if data[1] < statistic: data[0] = False
+    else: data[0] = True
+    data[3] = distribution.inverseCDF(confidence)[0]
+    if statistic < data[3]: data[4] = False
+    else: data[4] = True
+    return data
+
+def Z1Mean1Variance(smean, pmean, pvar, ssize, confidence):
+    """
+    Test 1: Z-test for a population mean (variance known)
+
+    To investigate the significance of the difference between an assumed
+    population mean and sample mean when the population variance is
+    known.
+
+    Limitations
+        1. Requires population variance (use Test 7 if population variance
+        unknown)
+
+    @param smean: sample mean
+    @param pmean: population mean
+    @param pvar: population variance
+    @param ssize: sample size
+    @param confidence: confidence level
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    statistic = float(abs(smean - pmean)/ \
+                (pvar / sqrt(ssize)))
+    return test(statistic, NormalDistribution(), confidence)
+
+def Z2Mean1Variance(smean1, smean2, pvar, ssize1, ssize2, confidence,
+                    pmean1=0.0, pmean2=0.0):
+    """
+    Test 2: Z-test for two population means (variances known and equal)
+
+    To investigate the significance of the difference between the means of two
+    samples when the variances are known and equal.
+
+    Limitations
+        1. Population variances must be known and equal (use Test 8 if
+        population variances unknown
+
+    @param smean1: sample mean of sample #1
+    @param smean2: sample mean of sample #2
+    @param pvar: variances of both populations (variances are equal)
+    @param ssize1: sample size of sample #1
+    @param ssize2: sample size of sample #2
+    @param confidence: confidence level
+    @param pmean1: population mean of population #1 (optional)
+    @param pmean2: population mean of population #2 (optional)
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    statistic = float(((smean1 - smean2) - (pmean1 - pmean2))/ \
+                (pvar * sqrt((1.0 / ssize1) + (1.0 / ssize2))))
+    return test(statistic, NormalDistribution(), confidence)
+
+def Z2Mean2Variance(smean1, smean2, pvar1, pvar2, ssize1, ssize2, confidence,
+                    pmean1=0.0, pmean2=0.0):
+    """
+    Test 3: Z-test for two population means (variances known and unequal)
+
+    To investigate the significance of the difference between the means of two
+    samples when the variances are known and unequal.
+
+    Limitations
+        1. Population variances must be known(use Test 9 if population variances
+        unknown
+
+    @param smean1: sample mean of sample #1
+    @param smean2: sample mean of sample #2
+    @param pvar1: variance of population #1
+    @param pvar2: variance of population #2
+    @param ssize1: sample size of sample #1
+    @param ssize2: sample size of sample #2
+    @param confidence: confidence level
+    @param pmean1: population mean of population #1 (optional)
+    @param pmean2: population mean of population #2 (optional)
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    statistic = float(((smean1 - smean2) - (pmean1 - pmean2))/ \
+                sqrt((pvar1 / ssize1) + (pvar2 / ssize2)))
+    return test(statistic, NormalDistribution(), confidence)
+
+def Z1Proportion(spro, ppro, ssize, confidence):
+    """
+    Test 4: Z-test for a proportion (binomial distribution)
+
+    To investigate the significance of the difference between an assumed
+    proportion and an observed proportion.
+
+    Limitations
+        1. Requires sufficiently large sample size to use Normal approximation
+        to binomial
+
+    @param spro: sample proportion
+    @param ppro: population proportion
+    @param ssize: sample size
+    @param confidence: confidence level
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    statistic = float((abs(ppro - spro) - (1 / (2 * ssize)))/ \
+                sqrt((ppro * (1 - spro)) / ssize))
+    return test(statistic, NormalDistribution(), confidence)
+
+def Z2Proportion(spro1, spro2, ssize1, ssize2, confidence):
+    """
+    Test 5: Z-test for the equality of two proportions (binomial distribution)
+    To investigate the assumption that the proportions of elements from two
+    populations are equal, based on two samples, one from each population.
+
+    Limitations
+        1. Requires sufficiently large sample size to use Normal approximation
+        to binomial
+
+    @param spro1: sample proportion #1
+    @param spro2: sample proportion #2
+    @param ssize1: sample size #1
+    @param ssize2: sample size #2
+    @param confidence: confidence level
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    P = float(((spro1 * ssize1) + (spro2 * ssize2)) / (ssize1 + ssize2))
+    statistic = float((spro1 - spro2) / \
+                (P * (1.0 - P) * ((1.0 / ssize1) + (1.0 / ssize2))) ** 0.5)
+    return test(statistic, NormalDistribution(), confidence)
+
+def Z2Count(time1, time2, count1, count2, confidence):
+    """
+    Test 6: Z-test for comparing two counts (Poisson distribution)
+
+    To investigate the significance of the differences between two counts.
+
+    Limitations
+        1. Requires sufficiently large sample size to use Normal approximation
+        to binomial
+
+    @param time1: first measurement time
+    @param time2: second measurement time
+    @param count1: counts at first measurement time
+    @param count2: counts at second measurement time
+    @param confidence: confidence level
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    R1 = count1 / float(time1)
+    R2 = count2 / float(time2)
+    statistic = float((R1 - R2) / sqrt((R1 / time1) + (R2 / time2)))
+    return test(statistic, NormalDistribution(), confidence)
+
+def t1Mean(smean, pmean, svar, ssize, confidence):
+    """
+    Test 7: t-test for a population mean (population variance unknown)
+
+    To investigate the significance of the difference between an assumed
+    population mean and a sample mean when the population variance is
+    unknown and cannot be assumed equal or not equal.
+
+    Limitations
+        1. Weaker form of Test 1
+
+    @param smean: sample mean
+    @param pmean: population mean
+    @param svar: sample variance
+    @param ssize: sample size
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    statistic = float((smean - pmean) / (svar / sqrt(ssize)))
+    return test(statistic, TDistribution(shape = ssize-1), confidence)
+
+def t2Mean2EqualVariance(smean1, smean2, svar1, svar2, ssize1, ssize2,
+                         confidence, pmean1=0.0, pmean2=0.0):
+    """
+    Test 8: t-test for two population means (population variance unknown but
+    equal)
+
+    To investigate the significance of the difference between the means of
+    two populations when the population variances are unknown but assumed
+    equal.
+
+    Limitations
+        1. Weaker form of Test 2
+
+    @param smean1: sample mean of sample #1
+    @param smean2: sample mean of sample #2
+    @param svar1: variances of sample #1
+    @param svar2: variances of sample #2
+    @param ssize1: sample size of sample #1
+    @param ssize2: sample size of sample #2
+    @param confidence: confidence level
+    @param pmean1: population mean of population #1 (optional)
+    @param pmean2: population mean of population #2 (optional)
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    df = ssize1 + ssize2 - 2
+    pvar = float((((ssize1 - 1) * svar1) + ((ssize2 - 1) * svar2)) / df)
+    statistic = float(((smean1 - smean2) - (pmean1 - pmean2)) / \
+                ((sqrt(pvar)) * sqrt((1.0 / ssize1) + (1.0 / ssize2))))
+    return test(statistic, TDistribution(shape = df), confidence)
+
+def t2Mean2UnequalVariance(smean1, smean2, svar1, svar2, ssize1, ssize2,
+                           confidence, pmean1=0.0, pmean2=0.0):
+    """
+    Test 9: t-test for two population means (population variance unknown and
+    unequal)
+
+    To investigate the significance of the difference between the means of
+    two populations when the population variances are unknown and unequal.
+
+    Limitations
+        1. Weaker form of Test 3
+
+    @param smean1: sample mean of sample #1
+    @param smean2: sample mean of sample #2
+    @param svar1: variances of sample #1
+    @param svar2: variances of sample #2
+    @param ssize1: sample size of sample #1
+    @param ssize2: sample size of sample #2
+    @param confidence: confidence level
+    @param pmean1: population mean of population #1 (optional)
+    @param pmean2: population mean of population #2 (optional)
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    statistic = float(((smean1 - smean2) - (pmean1 - pmean2)) / \
+                sqrt((svar1 / ssize1) + (svar2 / ssize2)))
+    df = float((((svar1 / ssize1) + (svar2 / ssize2)) ** 2) / \
+        (((svar1 ** 2) / ((ssize1 ** 2) * (ssize1 - 1))) + \
+            ((svar2 ** 2) / ((ssize2 ** 2) * (ssize2 - 1)))))
+    return test(statistic, TDistribution(shape = df), confidence)
+
+def tPaired(smean1, smean2, svar, ssize, confidence):
+    """
+    Test 10: t-test for two population means (method of paired comparisons)
+
+    To investigate the significance of the difference between two population
+    means when no assumption is made about the population variances.
+
+    @param smean1: sample mean of sample #1
+    @param smean2: sample mean of sample #2
+    @param svar: variance of differences between pairs
+    @param ssize: sample size
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    statistic = float((smean1 - smean2) / (svar / sqrt(ssize)))
+    return test(statistic, TDistribution(shape = ssize - 1), confidence)
+
+def tRegressionCoefficient(variancex, varianceyx, b, ssize, confidence):
+    """
+    Test 11: t-test of a regression coefficient
+
+    To investigate the significance of the regression coefficient.
+
+    Limitations
+        1. Homoedasticity of values
+
+    @param variancex: variance of x
+    @param varianceyx: variance of yx
+    @param b: calculated Regression Coefficient
+    @param ssize: sample size
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    statistic = float(((b * sqrt(variancex)) / \
+        sqrt(varianceyx)) * ((ssize-1) ** -0.5))
+    return test(statistic, TDistribution(shape = ssize - 2), confidence)
+
+def tPearsonCorrelation(r, ssize, confidence):
+    """
+    Test 12: t-test of a correlation coefficient
+
+    To investigate whether the difference between the sample correlation
+    coefficient and zero is statistically significant.
+
+    Limitations
+        1. Assumes population correlation coefficient to be zero (use Test 13
+        for testing other population correlation coefficient
+        2. Assumes a linear relationship (regression line as Y = MX + C)
+        3. Independence of x-values and y-values
+
+    Use Test 59 when these conditions cannot be met
+
+    @param r: calculated Pearson's product-moment correlation coefficient
+    @param ssize: sample size
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    statistic = float((r * sqrt(ssize - 2)) / sqrt(1 - (r **2)))
+    return test(statistic, TDistribution(shape = ssize - 2), confidence)
+
+def ZPearsonCorrelation(sr, pr, ssize, confidence):
+    """
+    Test 13: Z-test of a correlation coefficient
+
+    To investigate the significance of the difference between a correlation
+    coefficient and a specified value.
+
+    Limitations
+        1. Assumes a linear relationship (regression line as Y = MX + C)
+        2. Independence of x-values and y-values
+
+    Use Test 59 when these conditions cannot be met
+
+    @param sr: calculated sample Pearson's product-moment correlation
+        coefficient
+    @param pr: specified Pearson's product-moment correlation coefficient
+        to test
+    @param ssize: sample size
+    @param confidence: confidence level
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if sr == 1.0: sr = 0.9999999999
+    if pr == 1.0: pr = 0.9999999999
+    Z1 = float(0.5 * log((1 + sr) / (1 - sr)))
+    meanZ1 = float(0.5 * log((1 + pr) / (1 - pr)))
+    sigmaZ1 = float(1.0 / sqrt(ssize - 3))
+    statistic = float((Z1 - meanZ1) / sigmaZ1)
+    return test(statistic, NormalDistribution(), confidence)
+
+def Z2PearsonCorrelation(r1, r2, ssize1, ssize2, confidence):
+    """
+    Test 14: Z-test for two correlation coefficients
+
+    To investigate the significance of the difference between the correlation
+    coefficients for a pair variables occurring from two difference
+    populations.
+
+    @param r1: Pearson correlation coefficient of sample #1
+    @param r2: Pearson correlation coefficient of sample #2
+    @param ssize1: Sample size #1
+    @param ssize2: Sample size #2
+    @param confidence: confidence level
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    z1 = float(0.5 * log((1.0 + r1) / (1.0 - r1)))
+    z2 = float(0.5 * log((1.0 + r2) / (1.0 - r2)))
+    sigma1 = float(1.0 / sqrt(ssize1 - 3))
+    sigma2 = float(1.0 / sqrt(ssize2 - 3))
+    sigma = float(sqrt((sigma1 ** 2) + (sigma2 ** 2)))
+    statistic = float(abs(z1 - z2) / sigma)
+    return test(statistic, NormalDistribution(), confidence)
+
+def ChiSquarePopVar(values, ssize, pv, confidence = 0.95):
+    """
+    Test 15: Chi-square test for a population variance
+
+    To investigate the difference between a sample variance and an assumed
+    population variance.
+
+    @param values: sample values
+    @param ssize: sample size
+    @param pv: population variance
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    mean = sum(values)/ssize
+    freq = [float((values[i] - mean) ** 2)
+            for i in range(len(values))]
+    sv = float((sum(freq)/(ssize-1)))
+    statistic = float((((ssize - 1) * sv) / pv))
+    return test(statistic, ChiSquareDistribution(df = ssize-1), confidence)
+
+def FVarianceRatio(var1, var2, ssize1, ssize2, confidence):
+    """
+    Test 16: F-test for two population variances (variance ratio test)
+
+    To investigate the significance of the difference between two population
+    variances.
+
+    @param var1: variance #1
+    @param var2: variance #2
+    @param ssize1: sample size #1
+    @param ssize2: sample size #2
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    statistic = float(var1 / var2)
+    return test(statistic, FDistribution(df1=ssize1-1, df2=ssize2-1),
+    confidence)
+
+def F2CorrelatedObs(r, var1, var2, ssize1, ssize2, confidence):
+    """
+    Test 17: F-test for two population variances (with correlated observations)
+
+    To investigate the difference between two population variances when there
+    is correlation between the pairs of observations.
+    @param r: Sample correlation value
+    @param var1: variance #1
+    @param var2: variance #2
+    @param ssize1: sample size #1
+    @param ssize2: sample size #2
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    statistic = float(((var1 / var2)- 1) / (((((var1 / var2) + 1) ** 2) - \
+        (4 * (r ** 2) * (var1 / var2))) ** 0.5))
+    return test(statistic, FDistribution(ssize1-1, ssize2-1), confidence)
+
+#def t18(**kwargs):
+#    """
+#    Test 18: Hotelling's T-square test for two series of population means
+#
+#    To compare the results of two experiments, each of which yields a
+#    multivariate result. In another words, we wish to know if the mean pattern
+#    obtained from the first experiment agrees with the mean pattern obtained
+#    for the second.
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t19(**kwargs):
+#    """
+#    Test 19: Discriminant test for the origin of a p-fold sample
+#
+#    To investigate the origin of one species of values for p random variates,
+#    when one of two markedly different populations may have produced that
+#    particular series."""
+#    return test(statistic, Distribution(), confidence)
+
+# def Fishercumulant(m1, m2, m3, m4, ssize, confidence):
+    # """Test 20: Fisher's cumulant test for normality of a population
+
+    # To investigate the significance of the difference between frequency
+    # distribution based on a given sample and normal frequency distribution
+    # with same mean and variance
+
+    # Limitations:
+        # 1. Sample size should be large, >50
+        # 2. Distributions must have same mean and same variance
+
+    # @param m1: sample moment #1
+    # @param m2: sample moment #2
+    # @param m3: sample moment #3
+    # @param m4: sample moment #4
+    # @param ssize: sample size
+    # @param confidence: confidence level"""
+    # k1 = float(m1 / ssize)
+    # k2 = float((ssize * m2) - (m1 **2)) / (ssize * (ssize - 1))
+    # k3 = float(((ssize **2) * m3) - (3 * ssize * m2 * m1) + (2 * (m1 **3))) / \
+        # (ssize * (ssize-1) * (ssize-2))
+    # A = float(((ssize **3) + ssize **2) * m4)
+    # B = float(-4 * (((ssize **2) + ssize) * m3 * m1))
+    # C = float(-3 * ((ssize **2) * (m2 **2)))
+    # C2 = float(-3 * (-ssize * (m2 **2)))
+    # D = float(12 * m2 * (m1 **2))
+    # E = float(6 * (m1 **4))
+    # F = ssize * (ssize-1) * (ssize - 2) * (ssize - 3)
+    # k4 = float((A + B + C + C2 + D - E) / F)
+    # skewness = (k3 / (k2 * (k2 ** 0.5))) * ((ssize / 6 ) **0.5)
+    # kurtosis = (k4 / (k2 **2)) * ((ssize / 24 ) **0.5)
+    # statistic = (skewness **2) + (kurtosis **2)
+    # print A, B, C, C2, D, E, F, k4, kurtosis, statistic
+    # return test(statistic, ChiSquareDistribution(df = ssize - 1), confidence)
+
+# def DixonTest(values, n, confidence):
+    # """
+    # Test 21: Dixon's test for outliers
+
+    # To investigate the significance of the difference between a suspicious
+    # extreme value and other values in the sample.
+
+    # Limitations:
+    # 1. sample size should be greater than 3
+    # 2. Population which is being sampled is assumed normal
+
+    # @param values: list of values that are arranged in ascending order
+    # @param n: sample size
+    # @param confidence: confidence level"""
+    # if n in range(4, 8):
+        # statistic = float((values[1]) - (values[0])) / \
+        # float((values[n-1]) - (values[0]))
+    # elif n in range(8, 11):
+        # statistic = float(values[1] - values[0]) / \
+        # float(values[n-2] - values[0])
+    # elif n in range(11, 14):
+        # statistic = float(values[2] - values[0]) / \
+        # float(values[n-2] - values[0])
+    # elif n in range(14, 26):
+        # statistic = float(values[2] - values[0]) / \
+        # float(values[n-3] - values[0])
+    # else: print 'Sample size is larger than 25 or smaller than 3'
+    # return test(statistic, NormalDistribution(), confidence)
+
+# def FTestAnalysisofVar(s, k, confidence):
+    # """
+    # Test 22: F-test for K population means (analysis of variance)
+
+    # To test the null hypothesis that K samples are from K populations with
+    # the same mean
+
+    # Limitations:
+        # 1. It is assumed that the populations are normally distributed
+        # and have equal variances and samples are independent of each other
+
+    # @param s: sample datas
+    # @param k: number of populations
+    # @param confidence: confidence level"""
+    # Total = sum([sum(x) for x in s])
+    # Mean = [float(sum(x)) / float(len(x)) for x in s]
+    # N = sum([len(x) for x in s])
+    # s0 = [Mean[i] * Mean[i] * len(s[i])
+            # for i in range(len(Mean))]
+    # s1 = sum(s0) - A
+    # s3 = float((1/k)) * Total
+    # s4 = [(len(s[i]) * (Mean[i] - s3))
+        # for i in range(len(Mean))]
+    # s2 = float((sum(s4))) / float((N - k))
+    # statistic = float((s1 - s2)) / float((N - k))
+    # return test(statistic, FDistribution(df1 = k, df2 = len(x)), confidence)
+
+def ZCorrProportion(ssize, ny, yn, confidence):
+    """
+    Test 23: Z-test for correlated proportions
+
+    To investigate the significance of the difference between two correlated
+    proportions in opinion surveys. It can also be used for more general
+    applications.
+
+    Limitations
+        1. The same people are questioned both times (correlated property).
+        2. Sample size must be quite large.
+
+    @param ssize: sample size
+    @param ny: number answered 'no' in first poll and 'yes' in second poll
+    @param yn: number answered 'yes' in first poll and 'no' in second poll
+    @param confidence: confidence level
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    sigma = float((ny + yn) - (((ny - yn) ** 2.0) / ssize))
+    sigma = float(sqrt(sigma / (ssize * (ssize - 1.0))))
+    statistic = float((ny - yn) / (sigma * ssize))
+    return test(statistic, NormalDistribution(), confidence)
+
+def Chisq2Variance(ssize, svar, pvar, confidence):
+    """
+    Test 24: Chi-square test for an assumed population variance
+
+    To investigate the significance of the difference between a population
+    variance and an assumed variance value.
+
+    Limitations
+        1. Sample from normal distribution
+
+    @param ssize: sample size
+    @param svar: sample variance
+    @param pvar: population variance (assumed)
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    statistic = float((ssize-1) * svar/pvar)
+    return test(statistic, ChiSquareDistribution(df = ssize - 1), confidence)
+
+def F2Count(count1, count2, confidence, time1=0, time2=0, repeat=False):
+    """
+    Test 25: F-test for two counts (Poisson distribution)
+
+    To investigate the significance of the difference between two counted
+    results (based on a Poisson distribution).
+
+    Limitations
+        1. Counts must satisfy a Poisson distribution
+        2. Samples obtained under same conditions.
+
+    @param count1: count of first sample
+    @param count2: count of second sample
+    @param repeat: flag for repeated sampling (default = False)
+    @param time1: time at which first sample is taken
+        (only needed if repeat = True)
+    @param time2: time at which second sample is taken
+        (only needed if repeat = True)
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    if not repeat:
+        statistic = float(count1) / float(count2 + 1)
+        numerator = 2 * (count2 + 1)
+        denominator = 2 * count1
+    else:
+        statistic = (float(count1 + 0.5) / float(time1)) / \
+                    (float(count2 + 0.5) / float(time2))
+        numerator = 2 * count1 + 1
+        denominator = 2 * count2 + 1
+    return test(statistic, FDistribution(df1 = numerator,
+        df2 = denominator), confidence)
+
+#def t26(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t27(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t28(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t29(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t30(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t31(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t32(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t33(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t34(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t35(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def tKolmogorovSmirnov():
+#    """Test 36: The Kolmogorov-Smirnov test for comparing two populations
+#
+#    To investigate the significance of the difference between two population
+#    distributions based on the two sample distributions
+#
+#    Limitations
+#        1. Best results obtained when samples are sufficiently large, 15
+#        samples or more
+#    """
+#    return test(statistic, Distribution(), confidence)
+
+def ChisqFit(observed, expected, confidence):
+    """
+    Test 37: Chi-square test for goodness of fit
+
+    To investigate the significance of the differences between observed data
+    arranged in K classes, and the theoretical expected frequencies in the
+    K classes.
+
+    Limitations
+        1. Observed and theoretical distributions should have same number of
+        elements
+        2. Same class division for both distributions
+        3. Expected frequency of each class should be at least 5
+
+    @param observed: list of observed frequencies (index matched with expected)
+    @param expected: list of expected frequencies (index matched with observed)
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    freq = [float((observed[i] - expected[i]) ** 2) / (float(expected[i]))
+            for i in range(len(observed))]
+    statistic = 0.0
+    for x in freq: statistic = statistic + x
+    return test(statistic, ChiSquareDistribution(df = len(observed) - 1),
+                confidence)
+
+def tx2testofKcounts(T, V, confidence):
+    """
+    Test 38: The x2-test for compatibility of K counts
+
+    To investigate the significance of the differences between K counts.
+
+    Limitations:
+        1. The counts must be obtained under comparable conditions
+
+    @param T: list of time under K counts
+    @param V: list of values of K counts
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    R = float(sum(V)) / float(sum(T))
+    freq = [((V[i] - (T[i] * R)) ** 2) / float(T[i] * R)
+            for i in range(len(V))]
+    statistic = sum(freq)
+    return test(statistic, ChiSquareDistribution(df = len(V) - 1), confidence)
+
+#def t39(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+
+def Chisq2x2(s1, s2, ssize, confidence):
+    """
+    Test 40: Chi-square test for consistency in 2x2 table
+
+    To investigate the significance of the differences between observed
+    frequencies for two dichotomous distributions.
+
+    Limitations
+        1. Total sample size (sample 1 + sample 2) must be more than 20
+        2. Each cell frequency more than 3
+
+    @param s1: 2-element list or tuple of frequencies for sample #1
+    @param s2: 2-element list or tuple of frequencies for sample #2
+    @param ssize: sample size
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    s1c1 = s1[0]
+    s1c2 = s1[1]
+    s2c1 = s2[0]
+    s2c2 = s2[1]
+    statistic = (float(ssize - 1) * ((float(s1c1 * s2c2) - \
+        float(s1c2 * s2c1))**2)) / (float(s1c1 + s1c2) * float(s1c1 + s2c1) * \
+        float(s1c2 + s2c2) * float(s2c1 + s2c2))
+    return test(statistic, ChiSquareDistribution(df = 1), confidence)
+
+def ChisquareKx2table(c1, c2, k, confidence):
+    """Test 41: The x2-test for consistency in a K x 2 table
+
+    To investigate the significance of the differences between K observed
+    frequency distributions with a dichotomous classification.
+
+    Limitations:
+        1. K sample sizes must be large enough.
+        2. It is usually assumed to be satisfied if the cell frequencies are
+        equal to 5
+
+    @param c1: class #1 values of sample k
+    @param c2: class #2 values of sample k
+    @param k: number of samples
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    z = sum(c1)
+    nx = sum(c2)
+    n = [c1[i] + c2[i] for i in range(len(c1))]
+    Total = sum(n)
+    A = [x ** 2 for x in c1]
+    B = sum([float(A[i]) / float(n[i]) for i in range(len(A))])
+    C = (B - (float(z ** 2) / (float(Total))))
+    D = float(Total ** 2) / float(z * (Total - z))
+    statistic = D * C
+    return test(statistic, ChiSquareDistribution(k-1), confidence)
+
+#def t42(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+
+def Chisquare2xKtable(s1, s2, k, confidence):
+    """Test 43: The x2-test for consistency in a 2 x K table
+
+    To investigate the significance of the differences between two
+    distributions based on two samples spread over K classes
+
+    Limitations:
+        1. The two samples are sufficiently large
+        2. The K classes when put together form a complete series
+
+    @param s1: sample #1 of class k
+    @param s2: sample #2 of class k
+    @param k: number of classes
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    N1 = sum(s1)
+    N2 = sum(s2)
+    Total = N1 + N2
+    n =[s1[i] + s2[i] for i in range(len(s1))]
+    e1 = [float(N1 * x) / float(N1 + N2) for x in n]
+    e2 = [float(N2 * x) / float(N1 + N2) for x in n]
+    A = [((s1[i] - e1[i]) **2) / e1[i] for i in range(len(s1))]
+    B = [((s2[i] - e2[i]) **2) / e2[i] for i in range(len(s2))]
+    statistic = sum([A[i] + B[i] for i in range(len(A))])
+    return test(statistic, ChiSquareDistribution(k-1), confidence)
+
+# def ChisquarePxQ(d, confidence):
+    # """Test 44: The x2-test for independence in a p x q table
+
+    # To investigate the difference in frequency when classified by one
+    # attribute after classification by a second attribute
+
+    # Limitations:
+        # 1. Sample should be sufficiently large. This condition will be
+        # satisfied if each cell frequency is greater than 5
+
+    # @param d: data given as rows
+    # @param confidence: confidence level"""
+    # p = len(d)
+    # q = len(d[0])
+    # n01 = [sum(d[i]) for i in range(q)
+    # return test(statistic, ChiSquareDistribution((p-1) * (q-1)), confidence)
+
+#def t45(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+
+# def t2MediansPairedObs(x, y, confidence):
+    # """Test 46: The sign test for two medians (paired observations)
+
+    # To investigate the significance of the difference between the medians of
+    # two distributions
+
+    # Limitations:
+        # 1. The observations in the two samples should be taken in pairs, one
+        # from each distribution, taken under similar conditions. It is not
+        # necessary that different pairs should be taken under similar conditions
+
+    # @param x: A list of medians #1
+    # @param y: A list of medians #2
+    # @param confidence: confidence level"""
+    # freq = [x[i] > y[i]
+            # for i in range(len(x))]
+    # statistic = sum(freq)
+    # return test(statistic, Distribution(), confidence)
+
+#def t47(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t2MeansPairedObs(x, y, confidence):
+#    """
+#    Test 48: The signed rank test fo two means (paired observations)
+#
+#    Limitations:
+#        1.Observations in the two samples should be taken in pairs, one
+#        from each distribution, taken under similar conditions. It is not
+#        necessary that different pairs should be taken under similar conditions
+#
+#    @param x: A list of Means
+#    @param y: A list of Means
+#    @param confidence: confidence level"""
+#    # freq = [x[i] - y[i]
+#            # for i in range(len(x))]
+#    # Rank the difference of means using 0 as the norm
+#    # Tabulate the number of minus signs and plus signs
+#    # If the number of minus>plus sign, add up the values of the rank of all minus rank values
+#    # If the number of plus>minus sign, add up the values of the rank of all plus rank values
+#    # return test(statistic, Distribution(), confidence)
+#
+#def WilcoxonInversion():
+#    """Test 49: The Wilcoxon inversion test(U-test)
+#
+#    """
+#    return test(statistic, Distribution(), confidence)
+
+def MedianTestfor2Pop(s1=(9, 6), s2=(6, 9), confidence=0.95):
+    """Test 50: The median test of two populations
+
+    To test if two random samples could have come from two populations with
+    same frequency distribution
+
+    Limitations:
+        1. The two samples are assumed to be reasonably large
+
+    @param s1: 2-element list or tuple of frequencies for sample #1
+    @param s2: 2-element list or tuple of frequencies for sample #2
+    @param confidence: confidence level
+
+    @see: Chay, ZE, Ling, MHT. 2010. COPADS, II: Chi-Square test, F-Test
+    and t-Test Routines from Gopal Kanji's 100 Statistical Tests. The Python
+    Papers Source Codes 2:3
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    ls1 = s1[0]
+    rs1 = s1[1]
+    ls2 = s2[0]
+    rs2 = s2[1]
+    N = ls1+ls2+rs1+rs2
+    statistic = float(((abs((ls1*rs2)-(ls2*rs1))-(N * 0.5))**2)*N) / \
+    ((ls1+ls2)*(ls1+rs1)*(ls2+rs2)*(rs1+rs2))
+    return test(statistic, ChiSquareDistribution(df=1), confidence)
+
+#def t51(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t52(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t53(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t54(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t55(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t56(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t57(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+
+def SpearmanCorrelation(ssize, confidence, R=None, series1=[], series2=[]):
+    """
+    Test 58: Spearman rank correlation test (paired observations)
+    To investigate the significance of the correlation between two series of
+    observations obtained in pairs.
+
+    Limitations
+        1. Assumes the two population distributions to be continuous
+        2. Sample size must be more than 10
+
+    @param R: sum of squared ranks differences
+    @param ssize: sample size
+    @param series1: ranks of series #1 (not used if R is given)
+    @param series2: ranks of series #2 (not used if R is given)
+    @param confidence: confidence level
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if R == None:
+        R = sum([((series1[i] - series2[i]) ** 2) for i in range(len(series1))])
+    statistic = (6.0 * R) - (ssize * ((ssize ** 2) - 1.0))
+    statistic = statistic / (ssize * (ssize + 1.0) * sqrt(ssize - 1.0))
+    return test(statistic, NormalDistribution(), confidence)
+
+#def t59(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t60(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t61(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t62(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t63(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t64(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t65(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t66(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t67(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t68(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t69(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t70(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t71(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t72(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t73(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t74(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t75(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t76(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t77(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t78(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t79(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t80(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t81(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t82(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t83(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+
+def ZtestLogOddsRatio(group1, group2, confidence):
+    """Test 84: Z-test for comparing sequential contingencies acoss two groups
+    using the 'log odds ratio'
+
+    To test the significance of the difference in sequential connections
+    across groups
+
+    Limitations:
+        1. This test is applicable when a logit transformation can be used and
+        2 X 2 contingency tables are available
+
+
+    @param group1: group #1 values (row = x, y) & (column a, b) Provide values
+    in xa, xb, ya, yb format
+    @param group2: group #2 values (row = x, y) & (column a, b) Provide values
+    in xa, xb, ya, yb format
+    @param confidence: confidence level"""
+    x1a1 = float(group1[0])
+    x1b1 = float(group1[1])
+    y1a1 = float(group1[2])
+    y1b1 = float(group1[3])
+    x2a2 = float(group2[0])
+    x2b2 = float(group2[1])
+    y2a2 = float(group2[2])
+    y2b2 = float(group2[3])
+    C = x1a1 * y1b1
+    D = y1a1 * x1b1
+    G = C/D
+    E = x2a2 * y2b2
+    F = y2a2 * x2b2
+    H = E/F
+    A = log(G, e)
+    B = log(H, e)
+    statistic = (A - B)/ (((1/x1a1) + (1/x1b1) + (1/y1a1) + (1/y1b1) + \
+    (1/x2a2) + (1/x2b2) + (1/y2a2) + (1/y2b2)) **0.5)
+    return test(statistic, NormalDistribution(), confidence)
+
+#def t85(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t86(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t87(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t88(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t89(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t90(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t91(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+
+#def DubinWatsontest(**kwargs):
+#    """
+#    Test 92: Dubin-Watson test
+#
+#    To test whether the error terms in a regression model are autocorrelated
+#
+#    Limitations:
+#        This test is applicable if the autocorrelation parameter and error
+#        terms are independently normally distributed with mean zero and
+#        variance s2.
+#
+#    @param confidence: confidence level"""
+#    return test(statistic, Distribution(), confidence)
+#
+#def t93(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def ChisquareProbModel(**kwargs):
+#    """
+#    Test 94: x2-test for a suitable probabilistic model
+#
+#    Many experiments yield a set of data, say x1, x2, x3, xn and the
+#    experimenter often is interested in determining whether the data can be
+#    treated as the observed values of the random sample x1, x2, xn from a
+#    given distribution.
+#
+#    Limitations:
+#        this test is applicable if both distributions have the same interval
+#        classification and the number of elements. The observed data are
+#        observed by random sampling.
+#
+#    @param confidence: confidence level
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t95(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t96(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t97(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t98(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t99(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+#
+#def t100(**kwargs):
+#    """
+#    """
+#    return test(statistic, Distribution(), confidence)
+
diff --git a/copads/lindenmayer.py b/copads/lindenmayer.py
new file mode 100644
index 0000000..81a9cfb
--- /dev/null
+++ b/copads/lindenmayer.py
@@ -0,0 +1,446 @@
+'''
+Framework for Lindenmayer System (L-System)
+
+Copyright (c) Maurice H.T. Ling <mauriceling@acm.org>
+
+Date created: 4th January 2015
+'''
+import random
+
+import constants
+
+class lindenmayer(object):
+    '''
+    Lindenmayer system, also commonly known as L-System, is developed
+    by Aristid Lindenmayer in 1968 (reference: Mathematical models for
+    cellular interaction in development. Journal of Theoretical
+    Biology 18:280-315). It is a set of formal grammar of production
+    rules for rewiting an initial axiom or seed text over generations.
+
+    This implementation defines 3 types of rules, also known as production
+    rules or predicates; replacement, probability, and function rules. Each
+    rule can be given a priority.
+
+    The simplest form of production rule takes the form of 'A -> BAC', which
+    is read as "whenever 'A' is found, it is replaced/rewritten as 'BAC'".
+    For example, if the starting axiom is "A", then the following will happen
+
+        - Generation 0: A
+        - Generation 1: BAC
+        - Generation 2: BBACC
+        - Generation 3: BBBACCC
+        - Generation 4: BBBBACCCC
+        - and so on.
+
+    In this case, the predicate 'A -> BAC' can be written as in 4 different
+    ways - C{['A', 'BAC']}, C{['A', 'BAC', 1]}, C{['A', 'BAC', 1,
+    'replacement']}, or C{['A', 'BAC', 1, 'replacement', 1]}.
+
+    When a list of 2-elements is given (e.g., C{['A', 'BAC']}), it is taken
+    to be replacement rule with the highest priority; that is, priority of 1.
+    Hence, C{['A', 'BAC']}, C{['A', 'BAC', 1]}, C{['A', 'BAC', 1,
+    'replacement']}, and C{['A', 'BAC', 1, 'replacement', 1]} are the same.
+
+    This also means that production rules can have different priorities. For
+    example, given C{[['A', 'BAC', 1], ['B', 'BC', 2]]}, rule C{['A', 'BAC',
+    1]} will be executed before C{['B', 'BC', 2]} in the following manner
+
+        - Generation 0: A
+        - Generation 1: BCAC      # A -> BAC, BAC -> BCAC
+        - Generation 2: BCCBCACC
+        - Generation 3: BCCCBCCBCACCC
+        - and so on as all production rules in ascending order of priorities
+        (with '1' being the highest priority) will be executed in sequence on
+        the resulting axiom at that current point in time. As a result, the
+        order of rules within the same priority is significant if they
+        recognize the same instruction or command.
+
+    However, if given C{[['A', 'BAC', 1], ['B', 'BC', 1]]}, then
+
+        - Generation 0: A
+        - Generation 1: BAC
+        - Generation 2: BCBACC
+        - Generation 3: BCCBCBACCC
+        - Generation 4: BCCCBCCBCBACCCC
+        - and so on.
+
+    The second form of production rule is probabilistic, also known as
+    stochastic grammars. Probabilistic rule will take the format of
+    C{[<domain>, <range>, <priority>, 'probability', <probability>]}.
+    For example, C{['A', 'BAC', 1, 'probability', 0.5]} means that 'A'
+    will only be rewritten into 'BAC' 50% of the time. 'A' will be left
+    unchanged 50% of the time. The same priority principle applies.
+    Hence, C{['A', 'BAC', 1, 'probability', 1]} is in effect the same as
+    C{['A', 'BAC', 1, 'replacement']}.
+
+    The third form of production rule is function rule, which takes the
+    form of C{[<domain>, <function>, <priority>, 'function']}. For
+    example, C{['A', axiom_func, 1, 'function']} means that when 'A' is
+    encounted in the axiom, the command string up to that point in time
+    will be used as parameter for axion_func function, such as
+
+        - Generation 0: A
+        - Generation 1: dependent on the return value of axiom_func('A')
+        and so on.
+
+    For example, given an axiom of 'ACCCABABDD', and C{['AB', replaceFunction,
+    1, 'function']} as production rule where replaceFunction is defined as
+
+    >>> def replaceFunction(instructions, position):
+    >>>    if instructions[position+3] == 'O': return 'BAAB'
+    >>>    elif instructions[position-1] == 'O': return 'AABB'
+    >>>    else: return 'OOAB'
+
+        - Generation 0: ACCCABABDD
+        - Generation 1: ACCCOOABOOABDD
+        - Generation 2: ACCCOOBAABOOAABBDD
+        - Generation 3: ACCCOOBABAABOOAABBDD
+        - Generation 4: ACCCOOBABABAABOOAABBDD
+        - Generation 5: ACCCOOBABABABAABOOAABBDD
+
+    In summary, the following rule formats are allowed:
+        - C{[<domain>, <range>]}
+        - C{[<domain>, <range>, <priority>]}
+        - C{[<domain>, <range>, <priority>, 'replacement']}
+        - C{[<domain>, <range>, <priority>, 'probability', <probability>]}
+        - C{[<domain>, <function>, <priority>, 'function']}
+    '''
+    def __init__(self, command_length=1):
+        '''
+        Constructor method.
+
+        @param command_length: length of each instruction or command.
+        Default = 1
+        @type command_length: integer
+        '''
+        self.command_length = command_length
+        self.rules = []
+
+    def add_rules(self, rules):
+        '''
+        Method to add a list of production rules / predicates into the
+        system.
+
+        @param rules: a list of list describing the production rules. Please
+        see above for rule syntax.
+        @type rules: list
+        '''
+        for x in rules:
+            if len(x) == 2:
+                # [predicate, replacement]
+                self.rules.append([x[0], x[1], 1, 'replacement', None])
+            elif len(x) == 3:
+                # [predicate, replacement, priority]
+                self.rules.append([x[0], x[1], int(x[2]), 'replacement', None])
+            elif len(x) == 4 and (x[3] not in ['probability',
+                                               'replacement',
+                                               'function']):
+                # [predicate, replacement, priority, '<something_else>']
+                print('''Warning: Rule type can only be 'probabilistic', \
+                'replacement' or 'function'. Rule, %s, is not added into \
+                system.''' % str(x))
+            elif len(x) == 4 and x[3] == 'replacement':
+                # [predicate, replacement, priority, 'replacement']
+                self.rules.append([x[0], x[1], int(x[2]), x[3], None])
+            elif len(x) == 4 and x[3] == 'probability':
+                # [predicate, replacement, priority, 'probability']
+                print('''Warning: Function rule will require a \
+                probability. Rule, %s, is added into system as a \
+                replacement rule (100% activation probability).''' % str(x))
+                self.rules.append([x[0], x[1], int(x[2]), 'replacement', None])
+            elif len(x) == 5 and x[3] == 'probability':
+                # [predicate, replacement, priority, 'probability',
+                # probability]
+                self.rules.append([x[0], x[1], int(x[2]), x[3], float(x[4])])
+            elif len(x) == 4 and x[3] == 'function':
+                # [predicate, function, priority, 'function']
+                self.rules.append([x[0], x[1], int(x[2]), x[3], None])
+        self.priority_levels = [x[2] for x in self.rules][-1]
+
+    def _apply_priority_rules(self, priority, data_string):
+        '''
+        Private method - to be used by apply_rules method to apply production
+        rules of a particular priority.
+
+        @param priority: order of priority
+        @type priority: integer
+        @param data_string: data or symbol string to be processed
+        @type data_string: string
+        @return: rewritten data_string
+        '''
+        rules = [x for x in self.rules if x[2] == int(priority)]
+        ndata = ''
+        pointer = 0
+        while pointer < len(data_string):
+            cmd = data_string[pointer:pointer+self.command_length]
+            for rule in rules:
+                if cmd == rule[0] and rule[3] == 'replacement':
+                    cmd = rule[1]
+                    break
+                if cmd == rule[0] and rule[3] == 'probability' \
+                and random.random() < x[4]:
+                    cmd = rule[1]
+                    break
+                if cmd == rule[0] and rule[3] == 'function':
+                    cmd = rule[1](data_string, pointer)
+                    break
+            if cmd == None: cmd = ''
+            ndata = ndata + cmd
+            pointer = pointer + self.command_length
+        return ndata
+
+    def _apply_rules(self, data_string):
+        '''
+        Private method - to apply all production rules on axiom string (in
+        the first generation) or data/symbol string (in the subsequent
+        generations).
+        This method is implemented as a generator.
+
+        @param data_string: data or symbol string to be processed
+        @type data_string: string
+        @return: rewritten data_string
+        '''
+        for priority in list(range(1, self.priority_levels+1)):
+            data_string = self._apply_priority_rules(priority,
+                                                     data_string)
+        return data_string
+
+    def generate(self, axiom, iterations):
+        '''
+        Method to apply all production rules on an initial axiom string
+        over a number of iterations.
+
+        @param axiom: data or symbol string to be processed
+        @type axiom: string
+        @param iterations: number of repetitions / iterations
+        @type iterations: integer
+        @return: rewritten axiom
+        '''
+        self.axiom = axiom
+        iterations = int(iterations)
+        count = 1
+        while count < iterations + 1:
+            self.axiom = self._apply_rules(self.axiom)
+            print('Generation %s: Axiom length = %s' % (str(count),
+                                                        str(len(self.axiom))))
+            count = count + 1
+        return self.axiom
+
+    def turtle_generate(self, scriptfile=None, imagefile=None,
+                        start=(0, 0), mapping={}, data_string=None):
+        '''
+        Method for naive code generation to visualize the data or symbol
+        string using Turtle graphics. This method generates the Python
+        codes for Turtle graphics using the TK Turtle graphics module,
+        and prints out the resulting Python code as a file.
+
+        This method does not use any loops to reduce repetitive Turtle
+        commands; hence, the resulting code file can be huge.
+
+        A mapping dictionary is used to convert the symbol string into
+        Turtle commands. The following Turtle commands are defined:
+        forward, backward, right (turn), left (turn), pen up, pen down
+        and home. The default mapping is given as
+
+        >>> mapping = {'set_angle': 90,
+        >>>            'random_angle': 0,
+        >>>            'set_distance': 1,
+        >>>            'random_distance': 0,
+        >>>            'set_heading': 0,
+        >>>            'set_colour': 'black',
+        >>>            'background_colour': 'ivory',
+        >>>            'F': 'forward',
+        >>>            'B': 'backward',
+        >>>            'R': 'right',
+        >>>            'L': 'left',
+        >>>            'H': 'home',
+        >>>            'U': 'penup',
+        >>>            'D': 'pendown',
+        >>>            '[': 'push',
+        >>>            ']': 'pop'}
+
+        which can be read as
+            - a left or right turn is set at 90 degrees (set_angle). B{This
+            setting is mandatory.}
+            - random angles of turn can be set using 'random_angle', where
+            the actual angle will be from the set_angle to set_angle +
+            random_angle (by uniform distribution). For example, if
+            random_angle is 10 degrees, it means that the actual angle
+            at each turn will be uniformly distributed from 90 to 100
+            degrees. B{This will be set to 0 if not given.}
+            - each forward or backward move is set at 1 (set_distance).
+            B{This setting is mandatory.}
+            - random distance of each move can be set using 'random_
+            distance', following the same logic as 'random_angle'. B{This
+            will be set to 0 if not given.}
+            - turtle is set to head towards 0 degrees (east or to the
+            right of the screen). North (toward top), west (towards left),
+            and south (towards bottom) are 90, 180, 270 degrees
+            respectively. B{This will be set to 0 if not given.}
+            - default pen colour can be set using TK colour names as
+            'set_colour'. B{This will be set to black if not given.} Other
+            colours can be set and any un-used symbols (other than 'F', 'B',
+            'R', 'L', 'H', 'U', and 'D') can be used to set pen colours
+            (please see http://wiki.tcl.tk/37701 for available colours).
+            - canvas background colour can be set using background_colour.
+            B{This will be set to ivory if not given.}
+            - 'F', 'B', 'R', 'L', 'H', 'U', and 'D' represents the Turtle
+            commands of forward, backward, right turn, left turn, home,
+            pen up, and pen down respectively. Home is defined as the start
+            coordinate.
+            - '[' pushes the current state (position and heading) of the
+            Turtle into the stack.
+            - ']' pops (in a last in first out manner) and sets the Turtle
+            to the last pushed state without drawing the move.
+
+        @param scriptfile: file name to write out the Turtle commands.
+        Default = None, no file will be written
+        @type scriptfile: string
+        @param imagefile: SVG file name to write out Turtle graphics.
+        Default = None, no file will be written
+        @type imagefile: string
+        @param start: starting or home coordinate. Default = (0, 0) which
+        is the centre of the TK window
+        @type start: tuple
+        @param mapping: map to convert the symbol string into Turtle
+        commands. Please see explanation above.
+        @type mapping: dictionary
+        @param data_string: data or symbol string to be processed. Default =
+        None, internally stored axiom string (by lindenmayer.generate()
+        method) will be used instead.
+        @type data_string: string
+        @return: Python script file of Turtle commands
+        '''
+        if len(mapping) == 0:
+            mapping = {'set_angle': 90,
+                       'random_angle': 0,
+                       'set_distance': 1,
+                       'random_distance': 0,
+                       'set_heading': 0,
+                       'set_colour': 'black',
+                       'background_colour': 'ivory',
+                       'F': 'forward',
+                       'B': 'backward',
+                       'R': 'right',
+                       'L': 'left',
+                       'H': 'home',
+                       '[': 'push',
+                       ']': 'pop'}
+        if data_string == None:
+            data_string = self.axiom
+        stack = []
+        if 'random_angle' not in mapping:
+            mapping['random_angle'] = 0
+        if 'random_distance' not in mapping:
+            mapping['random_distance'] = 0
+        if 'set_heading' not in mapping:
+            mapping['set_heading'] = 0
+        if 'set_colour' not in mapping:
+            mapping['set_colour'] = 'black'
+        if 'background_colour' not in mapping:
+            mapping['background_colour'] = 'ivory'
+        data_string = [cmd for cmd in data_string if cmd in mapping]
+        if scriptfile != None:
+            f = open(scriptfile, 'w')
+            f.write("''' \n")
+            f.write('Turtle Graphics Generation from Lindenmayer System \n')
+            f.write('in COPADS (http://github.com/copads/copads) \n\n')
+            f.write('Code length = %s \n' % str(len(data_string)))
+            f.write('Code string = %s \n' % ''.join(data_string))
+            f.write('Code mapping = %s \n' % (mapping))
+            f.write("''' \n\n")
+            f.write('import turtle \n\n')
+            f.write('t = turtle.Turtle() \n')
+            f.write('t.setundobuffer(1) \n')
+            f.write("turtle.bgcolor('%s') \n" % mapping['background_colour'])
+            f.write('t.speed(0) \n\n')
+            f.write('t.setheading(%s) \n' % float(mapping['set_heading']))
+            f.write('t.penup() \n')
+            f.write('t.setposition(%s, %s) \n' % start)
+            f.write("t.pencolor('%s') \n" % mapping['set_colour'])
+            f.write('t.pendown() \n\n')
+        import turtle
+        t = turtle.Turtle()
+        t.setundobuffer(1)
+        exec("turtle.bgcolor('%s')" % mapping['background_colour'])
+        exec('t.speed(0)')
+        exec('t.setheading(%s)' % float(mapping['set_heading']))
+        exec('t.penup()')
+        exec('t.setposition(%s, %s)' % start)
+        exec("t.pencolor('%s')" % mapping['set_colour'])
+        exec('t.pendown()')
+        count = 0
+        for cmd in data_string:
+            count = count + 1
+            if count % 1000 == 0:
+                print('%s instructions processed ...' % str(count))
+            if mapping[cmd] == 'push':
+                status = [t.position(), t.heading()]
+                stack.append(status)
+            if mapping[cmd] == 'pop':
+                try:
+                    status = stack.pop()
+                    if scriptfile != None:
+                        f.write('t.penup() \n')
+                        f.write('t.setposition(%s, %s) \n' % status[0])
+                        f.write('t.setheading(%s) \n' % status[1])
+                        f.write('t.pendown() \n')
+                    exec('t.penup()')
+                    exec('t.setposition(%s, %s)' % status[0])
+                    exec('t.setheading(%s)' % status[1])
+                    exec('t.pendown()')
+                except IndexError: pass
+            if mapping[cmd] == 'forward':
+                distance = mapping['set_distance'] + \
+                           random.random()*mapping['random_distance']
+                if scriptfile != None:
+                    f.write('t.forward(%s) \n' % (distance))
+                exec('t.forward(%s)' % (distance))
+            if mapping[cmd] == 'backward':
+                distance = mapping['set_distance'] + \
+                           random.random()*mapping['random_distance']
+                if scriptfile != None:
+                    f.write('t.backward(%s) \n' % (distance))
+                exec('t.backward(%s)' % (distance))
+            if mapping[cmd] == 'home':
+                if scriptfile != None:
+                    f.write('t.penup() \n')
+                    f.write('t.setposition(%s, %s) \n' % start)
+                    f.write('t.pendown()')
+                exec('t.penup()')
+                exec('t.setposition(%s, %s)' % start)
+                exec('t.pendown() \n')
+            if mapping[cmd] == 'right':
+                angle = mapping['set_angle'] + \
+                        random.random()*mapping['random_angle']
+                if scriptfile != None:
+                    f.write('t.right(%s) \n' % str(angle))
+                exec('t.right(%s)' % str(angle))
+            if mapping[cmd] == 'left':
+                angle = mapping['set_angle'] + \
+                        random.random()*mapping['random_angle']
+                if scriptfile != None:
+                    f.write('t.left(%s) \n' % str(angle))
+                exec('t.left(%s)' % str(angle))
+            if mapping[cmd] in constants.TKColours:
+                f.write("t.pencolor('%s') \n" % mapping[cmd])
+                exec("t.pencolor('%s')" % mapping[cmd])
+        exec('t.penup()')
+        exec('t.hideturtle()')
+        if imagefile != None:
+            exec('import canvasvg')
+            exec("canvasvg.saveall('%s', t.getscreen()._canvas)" % imagefile)
+            f.write('\n')
+            f.write('try: \n')
+            f.write('    import canvasvg \n')
+            f.write("    canvasvg.saveall('%s', t.getscreen()._canvas) \n" \
+                    % imagefile)
+            f.write('except ImportError: pass \n')
+        exec('turtle.done()')
+        print('%s instructions processed. Drawing completed.' % str(count))
+        if scriptfile != None:
+            f.write('\n')
+            f.write('t.penup() \n')
+            f.write('t.hideturtle() \n')
+            f.write('turtle.done() \n')
+            f.close()
diff --git a/copads/neural.py b/copads/neural.py
new file mode 100644
index 0000000..502ff61
--- /dev/null
+++ b/copads/neural.py
@@ -0,0 +1,1033 @@
+'''
+Framework for Neural Network Applications
+Date created: 15th June 2015
+License: Python Software Foundation License version 2
+'''
+
+import math
+import sqlite3 as s
+from random import random
+
+class BrainBase(object):
+    '''
+    Class to represent data structure of the neuronal network (also known as 
+    the brain), using SQLite database.
+    '''
+
+    def __init__(self, path):
+        '''
+        Constructor method - initialize a new brain, or connect to an existing 
+        brain.
+        
+        @param path: full path name of the brain file (implemented as an SQLite 
+        database.
+        @type path: string
+        '''
+        self.conn = s.connect(path)
+        self.cur = self.conn.cursor()
+        braincheck = self._check_brain()
+        if True not in braincheck:
+            self._format_brain()
+        elif False in braincheck:
+            self._repair_brain(braincheck)
+
+    def close_brain(self):
+        '''
+        Closing the brain file.
+        '''
+        self.conn.commit()
+        self.conn.close()
+            
+    def _check_brain(self):
+        '''
+        Private method - Checks for the presence of tables in the brain file, 
+        as a proxy for checking whether the supplied brain file (in 
+        constructor) is a new file or an existing file. If it is new brain, 
+        then the tables need to be created.
+        '''
+        results = [True, True, True, True]
+        try:
+            self.cur.execute('select max(ID) from neuron')
+        except:
+            results[0] = False
+        try:
+            self.cur.execute('select max(ID) from neuron_internal_states')
+        except:
+            results[1] = False
+        try:
+            self.cur.execute('select max(ID) from neuron_input_states')
+        except:
+            results[2] = False
+        try:
+            self.cur.execute('select max(source) from connectome')
+        except:
+            results[3] = False
+        return results
+        
+    def _format_brain(self):
+        '''
+        Private method - Setting up the tables needed for a new brain file.
+        '''
+        try: 
+            self.cur.execute('drop table logfile')
+        except s.OperationalError: 
+            pass
+        self.cur.execute('''
+            create table if not exists logfile (
+                ID integer primary key autoincrement,
+                event text not null)''')
+        try: 
+            self.cur.execute('drop table neuron')
+        except s.OperationalError: 
+            pass
+        self.cur.execute('''
+            create table if not exists neuron (
+                ID integer primary key autoincrement,
+                name text not null,
+                status text not null default 'alive')''')
+        try: 
+            self.cur.execute('drop table neuron_internal_states')
+        except s.OperationalError: 
+            pass
+        self.cur.execute('''
+            create table if not exists neuron_internal_states (
+                ID integer not null,
+                key text not null,
+                value text not null,
+                primary key (ID, key))''')
+        try: 
+            self.cur.execute('drop table neuron_input_states')
+        except s.OperationalError: 
+            pass
+        self.cur.execute('''
+            create table if not exists neuron_input_states (
+                ID integer not null,
+                sourceID text not null,
+                value text not null,
+                primary key (ID, sourceID))''')
+        try: 
+            self.cur.execute('drop table connectome')
+        except s.OperationalError: 
+            pass
+        self.cur.execute('''
+            create table if not exists connectome (
+                source integer not null,
+                destination integer not null,
+                key text not null default 'weight',
+                value text not null,
+                primary key (source, destination, key))''')
+        self.conn.commit()
+    
+    def _repair_brain(self, braincheck):
+        pass
+        
+    def log(self, message):
+        '''
+        Method to insert logging message.
+        
+        @param message: message to be logged.
+        @type message: string
+        '''
+        self.cur.execute('insert into logfile (event) values (?)', (message,))
+        self.conn.commit()
+        
+    def add_neuron(self, **kwargs):
+        '''
+        Method to add a new neuron into the brain.
+        
+        Logging messages::
+            - add neuron: ADDNEURON:<ID of new neuron>:<name of new neuron> 
+            - set initial neuron internal data: SETININED:<ID of new neuron>:
+            <initial neuronal data in dictionary string format>
+            
+        @param kwargs: initial neuronal data (as dictionary). The following 
+        mandatory fields will be added for each neuron::
+            - name: user-defined name for the neuron. Default = no_name.
+            - summation: type of summation functions. Allowable values are 
+            'summation' (averaged input values). Default = summation.
+            - transfer: type of transfer / activation functions, which will 
+            generate the output value of the neuron. Allowable values are 
+            'linear' (neuron output = output from summation function), 
+            'sigmoid' (sigmoid curve output). Default = linear.
+            - threshold: theshold value of the neuron. Default = 0.5.
+            - pstate: initial output value of the neuron. Default = generated 
+            random value between 0-1.
+        @type kwargs: dictionary
+        @return: ID of the new neuron
+        '''
+        if 'name' not in kwargs:
+            name = 'no_name'
+        else: 
+            name = str(kwargs['name'])
+            del kwargs['name']
+        if 'summation' not in kwargs:
+            kwargs['summation'] = 'summation'
+        if 'transfer' not in kwargs:
+            kwargs['transfer'] = 'linear'
+        if 'threshold' not in kwargs:
+            kwargs['threshold'] = '0.5'
+        if 'pstate' not in kwargs:
+            kwargs['pstate'] = str(random())
+        self.cur.execute('insert into neuron (name) values (?)', (name,))
+        ID = str(self.cur.lastrowid)
+        for key in kwargs:
+            self.cur.execute('''
+                insert into neuron_internal_states (ID, key, value) 
+                values (?,?,?)''', (ID, str(key), str(kwargs[key])))
+        self.log('ADDNEURON:' + ID + ':' + name)
+        self.log('SETININED:' + ID + ':' + str(kwargs))
+        self.conn.commit()
+        return ID
+        
+    def delete_neuron(self, ID):
+        '''
+        Method to delete a neuron from the brain and all associated synapses.
+        
+        Logging messages::
+            - delete source synapses: DELNES:<ID of neuron to delete>
+            - delete destination synapses: DELNED:<ID of neuron to delete>
+            - delete internal states: DELNIS:<ID of neuron to delete>
+            - delete input states: DELNIN:<ID of neuron to delete>
+            - delete neuron: DELNEU:<ID of neuron to delete>
+        
+        @param ID: ID of neuron to delete
+        @type ID: string
+        '''
+        self.cur.execute('''delete from connectome where source=:ID''', 
+                         {'ID': str(ID)})
+        self.log('DELNES:' + ID)
+        self.cur.execute('''delete from connectome where destination=:ID''',
+                         {'ID': str(ID)})
+        self.log('DELNED:' + ID)
+        self.cur.execute('''delete from neuron_internal_states where ID=:ID''',
+                         {'ID': str(ID)})
+        self.log('DELNIS:' + ID)
+        self.cur.execute('''delete from neuron_input_states where ID=:ID''',
+                         {'ID': str(ID)})
+        self.log('DELNIN:' + ID)
+        self.cur.execute('''delete from neuron where ID=:ID''',
+                         {'ID': str(ID)})
+        self.log('DELNEU:' + ID)
+        self.conn.commit()
+
+    def _check_neuron_presence(self, ID):
+        '''
+        Private method - Check whether a neuron is present (in existence).
+        
+        @param ID: ID of the neuron to check.
+        @type ID: string
+        @return: False if neuron is not found; True if neuron is found.
+        '''
+        self.cur.execute('select name from neuron where ID=:ID',
+                         {'ID': str(ID)})
+        if self.cur.fetchone() == None:
+            return False
+        else:
+            return True
+                         
+    def add_synapse(self, sourceID, destinationID):
+        '''
+        Method to add a new synapse (connection between 2 neurons) into the 
+        brain. When either of the neurons are not present in the brain, 1 or 2 
+        neurons (depending whether 1 or both neurons are not found) will be 
+        created; however, the ID(s) of the neuron(s) will not be the same as 
+        provided. A random weight between 0 to 1 (as synaptic state) will be 
+        added.
+        
+        Logging messages::
+            - add synapse: ADDSYN:<ID of source / originating neuron>:<ID of 
+            destination / sink neuron>:<initial synaptic weight in dictionary 
+            string format>
+            
+        @param sourceID: ID of source / originating neuron.
+        @type sourceID: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        '''
+        if self._check_neuron_presence(sourceID) == False:
+            sourceID = self.add_neuron()
+        if self._check_neuron_presence(destinationID) == False:
+            destinationID = self.add_neuron()
+        kwargs = {'weight': random()}
+        for key in kwargs:
+            self.cur.execute('''
+                insert into connectome (source, destination, key, value) 
+                values (?,?,?,?)''',
+                (sourceID, destinationID, str(key), str(kwargs[key])))
+        self.log('ADDSYN:' + sourceID + ':' + destinationID + ':' + str(kwargs))
+        self.conn.commit()
+        
+    def delete_synapse(self, sourceID, destinationID):
+        '''
+        Method to delete a synapse (connection between 2 neurons) in the brain.
+        
+        Logging messages::
+            - delete synapse: DELSYN:<ID of source / originating neuron>:<ID of 
+            destination / sink neuron>
+            
+        @param sourceID: ID of source / originating neuron.
+        @type sourceID: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        '''
+        self.cur.execute('''
+            delete from connectome where 
+            source=:source and destination=:destination''',
+            {'source': str(sourceID),
+             'destination': str(destinationID)})
+        self.log('DELSYN:' + sourceID + ':' + destinationID)
+        self.conn.commit()
+
+    def set_synapse_state(self, sourceID, destinationID, 
+                          value, state='weight'):
+        '''
+        Method to set a state in a synapse.
+        
+        Logging messages::
+            - set synaptic state: SETSYNST:<ID of source / originating neuron>:
+            <ID of destination / sink neuron>:<state>:<value>
+        
+        @param sourceID: ID of source / originating neuron.
+        @type sourceID: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        @param value: value of the state.
+        @type value: string
+        @param state: state name to set or update.
+        @type state: string
+        '''
+        self.cur.execute('''select value from connectome
+                            where source=:sourceID and 
+                            destination=:destinationID and 
+                            key=:key''', 
+                         {'sourceID': str(sourceID),
+                          'destinationID': str(destinationID),
+                          'key': str(state)})
+        if self.cur.fetchone() == None:
+            self.cur.execute('''insert into connectome 
+                                (source, destination, key, value) values 
+                                (?,?,?,?)''',
+                             (str(sourceID), str(destinationID), 
+                              str(state), value))
+        else: 
+            self.cur.execute('''update connectome
+                                set value=:value
+                                where source=:sourceID and 
+                                destination=:destinationID and
+                                key=:key''',
+                             {'value': value,
+                              'sourceID': str(sourceID),
+                              'destinationID': str(destinationID),
+                              'key': str(state)})
+        self.log('SETSYNST:' + sourceID + ':' + destinationID + ':' + \
+                 str(state) + ':' + str(value))
+        self.conn.commit()
+        
+    def get_synapse_state(self, sourceID, destinationID, state='weight'):
+        '''
+        Method to get value of a synaptic state.
+        
+        @type sourceID: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        @param state: state name to set or update. Default = weight.
+        @type state: string
+        @return: value of the synaptic state (if found), or None (if state is 
+        not found)
+        '''
+        self.cur.execute('''select value from connectome
+                            where source=:sourceID and 
+                            destination=:destinationID and 
+                            key=:key''', 
+                         {'sourceID': str(sourceID),
+                          'destinationID': str(destinationID),
+                          'key': str(state)})
+        value = self.cur.fetchone()
+        if value == None:
+            return None
+        else:
+            return str(value[0])
+        
+    def get_summation(self, ID):
+        '''
+        Method to get the summation function type of a neuron.
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @return: summation function type of a neuron (as string).
+        '''
+        self.cur.execute('''select value from neuron_internal_states
+                            where ID=:ID and key=:key''',
+                         {'ID': str(ID),
+                          'key': 'summation'})
+        return self.cur.fetchone()
+
+    def get_transfer(self, ID):
+        '''
+        Method to get the transfer / activation function type of a neuron.
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @return: transfer / activation function type of a neuron (as string).
+        '''
+        self.cur.execute('''select value from neuron_internal_states
+                            where ID=:ID and key=:key''',
+                         {'ID': str(ID),
+                          'key': 'transfer'})
+        return self.cur.fetchone()
+
+    def get_input_values(self, ID):
+        '''
+        Method to get the input values vector of a neuron.
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @return: list of input values.
+        '''
+        self.cur.execute('''select value from neuron_input_states
+                            where ID=:ID''',
+                         {'ID': str(ID)})
+        return [float(str(x[0])) for x in self.cur.fetchall()]
+
+    def set_neuron_state(self, ID, state, value):
+        '''
+        Method to set a specific state of a neuron.
+        
+        Logging messages::
+            - set neuron internals: SNINTERNAL:<ID of neuron>:<state type of 
+            neuron>:<value of the state>
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @param state: type of state to set.
+        @type state: string
+        @param value: value to set for the state.
+        '''
+        self.cur.execute('''select value from neuron_internal_states
+                            where ID=:ID and key=:key''', 
+                         {'ID': str(ID),
+                          'key': str(state)})
+        if self.cur.fetchone() == None:
+            self.cur.execute('''insert into neuron_internal_states 
+                                (ID, key, value) values (?,?,?)''',
+                             (str(ID), str(state), value))
+        else: 
+            self.cur.execute('''update neuron_internal_states
+                                set value=:value
+                                where ID=:ID and key=:key''',
+                             {'value': value,
+                              'ID': str(ID),
+                              'key': str(state)})
+        self.log('SNINTERNAL:' + ID + ':' + str(state) + ':' + str(value))
+        self.conn.commit()
+        
+    def get_neuron_state(self, ID, state='pstate'):
+        '''
+        Method to get a specific state of a neuron.
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @param state: type of state to get. Default = pstate.
+        @type state: string
+        @return: state value (if present); None (if state is not found).
+        '''
+        self.cur.execute('''select value from neuron_internal_states
+                            where ID=:ID and key=:key''',
+                         {'ID': str(ID),
+                          'key': str(state)})
+        value = self.cur.fetchone()
+        if value == None:
+            return None
+        else:
+            return str(value[0])
+
+    def get_next_neurons(self, ID, stype='weight'):
+        '''
+        Method to get a set of next connected neurons (the destination neurons) 
+        from a source neuron.
+        
+        @param ID: ID of source neuron.
+        @type ID: string
+        @param stype: type of synaptic state of the synapse (connection) to get. 
+        Default = weight.
+        @type stype: string
+        @return: list of tuples (<ID of destination neuron>, <value of stype>)
+        '''
+        self.cur.execute('''select destination, value 
+                            from connectome
+                            where source=:source and key=:stype''',
+                         {'source': str(ID),
+                          'stype': stype})
+        return [(str(x[0]), float(str(x[1])))
+                for x in self.cur.fetchall()]
+
+    def set_input_states(self, sourceID, destinationID, value):
+        '''
+        Method to set an input value of a neuron. It is not mandatory to have 
+        established a prior input channel, though recommended.
+        
+        Logging messages::
+            - set neuron input: SNINPUT:<ID of source / originating neuron>:<ID 
+            of destination / sink neuron>:<value of input>
+            
+        @param sourceID: ID of source / originating neuron.
+        @type sourceID: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        @param value: value of the input.
+        '''
+        self.cur.execute('''select sourceID, ID 
+                            from neuron_input_states
+                            where sourceID=:sourceID and
+                            ID=:destinationID''',
+                         {'sourceID': str(sourceID),
+                          'destinationID': str(destinationID)})
+        if self.cur.fetchone() == None:
+            self.cur.execute('''insert into neuron_input_states
+                                (ID, sourceID, value) values (?,?,?)''', 
+                             (destinationID, sourceID, value))
+        else:
+            self.cur.execute('''update neuron_input_states
+                                set value=:value
+                                where sourceID=:sourceID and
+                                ID=:destinationID''',
+                             {'value': value,
+                              'sourceID': str(sourceID),
+                              'destinationID': str(destinationID)})
+        self.log('SNINPUT:' + str(sourceID) + ':' + str(destinationID) + \
+                 ':' + str(value))
+        self.conn.commit()
+
+    def add_external_input(self, source, destinationID, value):
+        '''
+        Method to set add an external input channel for a neuron or set an 
+        external input value into a neuron.
+        
+        Logging messages::
+            - add external input: AEXTIN:<ID of source / originating neuron>:
+            <ID of destination / sink neuron>:<value of input>
+            
+        @param source: ID of source / originating neuron.
+        @type source: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        @param value: value of the input.
+        '''
+        self.cur.execute('''select sourceID, ID 
+                            from neuron_input_states
+                            where sourceID=:source and
+                            ID=:destinationID''',
+                         {'source': str(source),
+                          'destinationID': str(destinationID)})
+        if self.cur.fetchone() == None:
+            self.cur.execute('''insert into neuron_input_states
+                                (ID, sourceID, value) values (?,?,?)''', 
+                             (destinationID, source, value))
+        else:
+            self.cur.execute('''update neuron_input_states
+                                set value=:value
+                                where sourceID=:sourceID and
+                                ID=:destinationID''',
+                             {'value': value,
+                              'sourceID': str(source),
+                              'destinationID': str(destinationID)})
+        self.log('AEXTIN:' + str(source) + ':' + str(destinationID) + \
+                 ':' + str(value))
+        self.conn.commit()
+        
+    def get_neurons(self, status='alive'):
+        '''
+        Method to get a list of neurons in the brain according to status.
+        
+        @param status: status of neurons to get. Default = alive.
+        @type status: string
+        @return: list of neuron IDs.
+        '''
+        self.cur.execute('select ID from neuron where status=:status',
+                         {'status': str(status)})
+        return [str(x[0]) for x in self.cur.fetchall()]
+        
+    def get_synapses(self, source, stype='weight'):
+        '''
+        Method to get a list of neurons with a specific input / source type and 
+        synaptic type.
+        
+        @param source: source type (can be ID of source neuron, or name of 
+        external source)
+        @type source: string
+        @param stype: type of synapse. To get all types of synapse, stype = 
+        'all'. Default = weight. 
+        @type stype: string
+        @return: list of destination neuron IDs
+        '''
+        if stype == 'all':
+            self.cur.execute('''
+                select destination from connectome 
+                where source=:source''',
+                {'source': str(source)})
+        else:
+            self.cur.execute('''
+                select destination from connectome 
+                where source=:source and key=:stype''',
+                {'source': str(source),
+                 'stype': str(stype)})
+        return [str(x[0]) for x in self.cur.fetchall()]
+        
+    def get_all_synapses(self):
+        '''
+        Method to get all synapses.
+        
+        @return: list of all synapses in tuple of (<source ID>, <destination 
+        ID>)
+        '''
+        self.cur.execute('select distinct source, destination from connectome')
+        return [(str(x[0]), str(x[1])) for x in self.cur.fetchall()]
+        
+    def get_all_synaptic_states(self, stype='weight'):
+        '''
+        Method to get all synaptic states of a specific type.
+        
+        @param stype: type of synapse. Default = weight. 
+        @type stype: string
+        @return: list of synaptic values.
+        '''
+        self.cur.execute('''
+            select value from connectome 
+            where key=:key''',
+            {'key': str(stype)})
+        return [str(x[0]) for x in self.cur.fetchall()]
+        
+    def get_neuronID_from_name(self, name):
+        '''
+        Method to get neuron ID from its name.
+        
+        @param name: name of neuron.
+        @type name: string
+        @return: ID of neuron (if present); None (if not found).
+        '''
+        self.cur.execute('''select ID from neuron
+                            where name=:name''',
+                         {'name': str(name)})
+        value = self.cur.fetchone()
+        if value == None:
+            return None
+        else:
+            return str(value[0])
+            
+    def get_name_from_neuronID(self, ID):
+        '''
+        Method to get neuron name from its ID.
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @return: name of neuron (if present); None (if not found).
+        '''
+        self.cur.execute('''select name from neuron
+                            where ID=:ID''',
+                         {'ID': str(ID)})
+        value = self.cur.fetchone()
+        if value == None:
+            return None
+        else:
+            return str(value[0])
+        
+
+class Brain(object):
+    '''
+    Class to represent a neuronal network (also known as the brain), using 
+    SQLite database.
+    '''
+
+    def __init__(self, path):
+        '''
+        Constructor method - initialize a new brain, or connect to an existing 
+        brain.
+        
+        @param path: full path name of the brain file (implemented as an SQLite 
+        database.
+        @type path: string
+        '''
+        self.base = BrainBase(path)
+        self.sequence = {}
+        for ID in self.base.get_neurons('alive'):
+            self.sequence[ID] = None
+
+    def format(self):
+        self.base._format_brain()
+        
+    def add_neuron(self, **kwargs):
+        '''
+        Method to add a new neuron into the brain.
+            
+        @param kwargs: initial neuronal data (as dictionary). The following 
+        mandatory fields will be added for each neuron::
+            - name: user-defined name for the neuron. Default = no_name.
+            - summation: type of summation functions. Allowable values are 
+            'summation' (averaged input values). Default = summation.
+            - transfer: type of transfer / activation functions, which will 
+            generate the output value of the neuron. Allowable values are 
+            'linear' (neuron output = output from summation function), 
+            'sigmoid' (sigmoid curve output). Default = linear.
+            - threshold: theshold value of the neuron. Default = 0.5.
+            - pstate: initial output value of the neuron. Default = generated 
+            random value between 0-1.
+        @type kwargs: dictionary
+        @return: ID of the new neuron
+        '''
+        ID = self.base.add_neuron(**kwargs)
+        self.sequence[ID] = None
+        return ID
+
+    def add_synapse(self, sourceID, destinationID):
+        '''
+        Method to add a new synapse (connection between 2 neurons) into the 
+        brain. When either of the neurons are not present in the brain, 1 or 2 
+        neurons (depending whether 1 or both neurons are not found) will be 
+        created; however, the ID(s) of the neuron(s) will not be the same as 
+        provided. A random weight between 0 to 1 (as synaptic state) will be 
+        added.
+        
+        @param sourceID: ID of source / originating neuron.
+        @type sourceID: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        '''
+        self.base.add_synapse(sourceID, destinationID)
+        
+    def delete_neuron(self, ID):
+        '''
+        Method to delete a neuron from the brain and all associated synapses.
+        
+        @param ID: ID of neuron to delete
+        @type ID: string
+        '''
+        self.base.delete_neuron(ID)
+        del self.sequence[ID]
+        
+    def delete_synapse(self, sourceID, destinationID):
+        '''
+        Method to delete a synapse (connection between 2 neurons) in the brain.
+        
+        @param sourceID: ID of source / originating neuron.
+        @type sourceID: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        '''
+        self.base.delete_synapse(sourceID, destinationID)
+        
+    def _neuron_summation(self, ID):
+        '''
+        Private method - Calculate a single input (to be used as input for 
+        transfer / activation function) from a set of input vectors. If there 
+        is no summation function for the neuron or if the calculation returns 
+        an error (exception), 0.0 will be returned.
+        
+        @param ID: ID of neuron to calculate.
+        @type ID: string
+        @return: calculated input.
+        '''
+        sum_func = self.base.get_summation(ID)
+        if sum_func == None:
+            return 0.0
+        sum_func = str(sum_func[0])
+        neuron_input = self.base.get_input_values(ID)
+        try:
+            if sum_func == 'summation':
+                return sum(neuron_input) / len(neuron_input)
+        except: 
+            return 0.0
+
+    def _neuron_transfer(self, ID, neuron_input):
+        '''
+        Private method - Calculate the neuron output, using the transfer / 
+        activation function, from the calculated input of the summation 
+        function. If there is no transfer / activation function for the neuron 
+        or if the calculation returns an error (exception), 0.0 will be 
+        returned.
+        
+        @param ID: ID of neuron to calculate.
+        @type ID: string
+        @param neuron_input: output from _neuron_summation(), to be used as 
+        input for transfer / activation function.
+        @type neuron_input: float
+        @return: output of transfer / activation function.
+        '''
+        transfer = self.base.get_transfer(ID)
+        if transfer == None:
+            return 0.0
+        transfer = str(transfer[0])
+        try:
+            if transfer == 'linear':
+                return neuron_input
+            elif transfer == 'sigmoid':
+                return 1.0 / (1.0 + math.exp(-1.0 * neuron_input))
+        except: 
+            return 0.0
+
+    def _synaptic_activation(self, ID, state='pstate', stype='weight'):
+        '''
+        Private method - Calculate each synaptic activation from a source ID 
+        and log the values into the respective input vectors of the destination 
+        neurons.
+        
+        @param ID: ID of source neuron.
+        @type ID: string
+        @param state: state name of neuron as input for synaptic activation. 
+        Default = weight.
+        @type state: string
+        @param stype: name of synaptic state for activation. 
+        @type stype: string
+        '''
+        sourceID = ID
+        source_pstate = self.base.get_neuron_state(ID, state)
+        for n in self.base.get_next_neurons(ID, stype):
+            destinationID = n[0]
+            weight = n[1]
+            value = weight * float(source_pstate)
+            self.base.set_input_states(sourceID, destinationID, value)
+
+    def execute_neuron(self, ID, state='pstate', stype='weight'):
+        '''
+        Method to execute a neuron. 
+        
+        The following tasks will be executed for a neuron::
+            1. calculate a value from the set of input value vectors
+            2. calculate the neuronal output value from (1) using the transfer / 
+            activation function
+            3. set a neuronal state as the neuronal output value from (2)
+            4. activate synapses (using _synaptic_activation function) to 
+            generate input vector(s) for destination neuron(s).
+        
+        @param ID: ID of the neuron to execute.
+        @type ID: string
+        @param state: state of neuron to store output from transfer / 
+        activation function. Default = pstate.
+        @type state: string
+        @param stype: type of synapse for synaptic activation. Default = weight.
+        @type stype: string
+        '''
+        neuron_input = self._neuron_summation(ID)
+        pstate = self._neuron_transfer(ID, neuron_input)
+        self.base.set_neuron_state(ID, 'pstate', pstate)
+        self._synaptic_activation(ID, state, stype)
+
+    def add_input_channel(self, source, destinationID):
+        '''
+        Method to add an input channel into a neuron from external source. A 
+        random value will be added as the synaptic weight.
+        
+        @param source: name of external source
+        @type source: string
+        @param destinationID: ID of target neuron.
+        @type destinationID: string
+        '''
+        self.base.add_external_input(source, destinationID, random())
+        
+    def get_neurons(self, status='alive'):
+        '''
+        Method to get a list of neurons in the brain according to status.
+        
+        @param status: status of neurons to get. Default = alive.
+        @type status: string
+        @return: list of neuron IDs.
+        '''
+        return self.base.get_neurons(status)
+        
+    def get_synapses(self, source, stype='weight'):
+        '''
+        Method to get a list of neurons with a specific input / source type and 
+        synaptic type.
+        
+        @param source: source type (can be ID of source neuron, or name of 
+        external source)
+        @type source: string
+        @param stype: type of synapse. To get all types of synapse, stype = 
+        'all'. Default = weight. 
+        @type stype: string
+        @return: list of destination neuron IDs
+        '''
+        return self.base.get_synapses(source, stype)
+    
+    def get_all_synapses(self):
+        '''
+        Method to get all synapses.
+        
+        @return: list of all synapses in tuple of (<source ID>, <destination 
+        ID>)
+        '''
+        return self.base.get_all_synapses()
+        
+    def get_input_values(self, ID):
+        '''
+        Method to get the input values vector of a neuron.
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @return: list of input values.
+        '''
+        return self.base.get_input_values(ID)
+    
+    def set_neuron_state(self, ID, state, value):
+        '''
+        Method to set a specific state of a neuron.
+        
+        Logging messages::
+            - set neuron internals: SNINTERNAL:<ID of neuron>:<state type of 
+            neuron>:<value of the state>
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @param state: type of state to set.
+        @type state: string
+        @param value: value to set for the state.
+        '''
+        self.base.set_neuron_state(ID, state, value)
+        
+    def get_neuron_state(self, ID, state='pstate'):
+        '''
+        Method to get a specific state of a neuron.
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @param state: type of state to get. Default = pstate.
+        @type state: string
+        @return: state value (if present); None (if state is not found).
+        '''
+        return self.base.get_neuron_state(ID, state)
+        
+    def set_synapse_state(self, sourceID, destinationID, 
+                          value, state='weight'):
+        '''
+        Method to set a state in a synapse.
+        
+        @param sourceID: ID of source / originating neuron.
+        @type sourceID: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        @param value: value of the state.
+        @type value: string
+        @param state: state name to set or update.
+        @type state: string
+        '''
+        self.base.set_synapse_state(sourceID, destinationID, 
+                                    value, state)
+        
+    def get_synapse_state(self, sourceID, destinationID, state='weight'):
+        '''
+        Method to get value of a synaptic state.
+        
+        @type sourceID: string
+        @param destinationID: ID of destination / sink neuron.
+        @type destinationID: string
+        @param state: state name to set or update. Default = weight.
+        @type state: string
+        @return: value of the synaptic state (if found), or None (if state is 
+        not found)
+        '''
+        return self.base.get_synapse_state(sourceID, destinationID, state)
+        
+    def get_all_synaptic_states(self, stype='weight'):
+        '''
+        Method to get all synaptic states of a specific type.
+        
+        @param stype: type of synapse. Default = weight. 
+        @type stype: string
+        @return: list of synaptic values.
+        '''
+        return self.base.get_all_synaptic_states(stype)
+        
+    def set_input(self, source, inputstates):
+        '''
+        Method to set input values (from external) into neurons.
+        
+        @param source: name of external source.
+        @type source: string
+        @param inputstates: input values from the external source in 
+        dictionary; where key is the neuron ID to set the input value, and 
+        value is the value. For example, {'1': 0.9, '2': 0.5} means set input 
+        for neuron ID 1 as 0.9 and input for neuron ID 2 as 0.5.
+        @type inputstates: dictionary
+        '''
+        for destinationID in inputstates:
+            self.base.set_input_states(source, destinationID, 
+                                       inputstates[destinationID])
+            
+    def get_neuronID_from_name(self, name):
+        '''
+        Method to get neuron ID from its name.
+        
+        @param name: name of neuron.
+        @type name: string
+        @return: ID of neuron (if present); None (if not found).
+        '''
+        return self.base.get_neuronID_from_name(name)
+        
+    def get_name_from_neuronID(self, ID):
+        '''
+        Method to get neuron name from its ID.
+        
+        @param ID: ID of neuron.
+        @type ID: string
+        @return: name of neuron (if present); None (if not found).
+        '''
+        return self.base.get_name_from_neuronID(ID)
+        
+    def delete_orphaned_neurons(self):
+        '''
+        Method to delete orphaned neurons (neurons without any synapses).
+        '''
+        synapses = self.get_all_synapses()
+        connected_synapses = set([x[0] for x in synapses])
+        connected_synapses = connected_synapses | set([x[1] for x in synapses])
+        connected_synapses = list(connected_synapses)
+        orphans = [x for x in self.sequence if x not in connected_synapses]
+        for ID in orphans:
+            self.delete_neuron(ID)
+            
+    def delete_synapse_by_state(self, threshold=0.1, mode='lowest', 
+                                stype='weight'):
+        '''
+        Method to delete synapse(s) by thresholding on synaptic states. There 
+        are a number of ways (criterion) to select synapses for deletion. The 
+        criterion is determined by the 'mode' parameter. 
+        
+        The following criteria (modes) are allowed::
+            - lowest: select the lowest percentage of synapses, by state value, 
+            for deletion. Threshold will determine the ratio. For example, if 
+            threshold = 0.1, means the lowest decile of the synapses, by state 
+            value, will be deleted.
+            - below: select the synapses with state value below the threshold 
+            value for deletion. For example, if threshold = 0.1, means that 
+            synapses with state value lower than 0.1, will be deleted.
+        
+        @param threshold: threshold for use in deletion (as ratio). Default = 
+        lowest.
+        @type threshold: float
+        @param mode: criteria for deletion. Default = lowest.
+        @type mode: string
+        @param stype: synaptic state for use in deletion criterion. Default = 
+        weight.
+        @type stype: float
+        '''
+        states = [float(x) for x in self.get_all_synaptic_states(stype)]
+        states.sort()
+        if mode == 'lowest':
+            states = states[:int(len(states)*float(threshold))]
+        elif mode == 'below':
+            states = [x for x in states if x < float(threshold)]
+        for value in states:
+            self.base.cur.execute('''
+                select source, destination from connectome where 
+                key=:key and value=:value''',
+                {'key': str(stype),
+                 'value': str(value)})
+            result = self.base.cur.fetchone()
+            sourceID = str(result[0])
+            destinationID = str(result[1])
+            self.delete_synapse(sourceID, destinationID)
+            
+    def generate_random_synapses(self, count):
+        '''
+        '''
+        nIDs = self.get_neurons('alive')
+        
diff --git a/copads/nrpy.py b/copads/nrpy.py
new file mode 100644
index 0000000..9f9dcab
--- /dev/null
+++ b/copads/nrpy.py
@@ -0,0 +1,1193 @@
+"""
+Numerical Recipes in Python.
+
+References:
+    - Press, William H., Flannery, Brian P., Teukolsky, Saul A., and Vetterling,
+    William T. 1989. Numerical Recipes in Pascal. Cambridge University Press,
+    Cambridge (ISBN 978-0521375160)
+    - Press, William H., Flannery, Brian P., Teukolsky, Saul A., and Vetterling,
+    William T. 1992. Numerical Recipes in C, 2nd edition. Cambridge University
+    Press, Cambridge (ISBN 978-0521431088)
+
+Numerical Recipes in C, 2nd edition is freely browsable online at
+http://www.nrbook.com/a/bookcpdf.php but is not intended as a substitution
+for purchasing the book.
+
+Functions will be named as in the references and will be referred to section
+number. For example, the reference "NRP 5.2" refers to Numerical Recipes
+in Pascal chapter 5 section 2.
+
+The authors of Numerical Recipes in Pascal (NRP) and Numerical Recipes in
+C, 2nd edition (NRC2) explicitly allows the reader to analyze the mathematical
+ideas in the codes within the book and owns the re-implemented functions as
+stated in NRP and NRC2 that "If you analyze the ideas contained in a program,
+and then express those ideas in your own distinct implementation, then that new
+program implementation belongs to you" (page xv of NRP; page xviii of NRC2).
+Not mentioned in NRP, NRC2 allows the reader a "free licence" (page xviii of
+NRC2) which allows the reader to make one machine-readable copy of the C codes
+in the book for his/her own use (not distribution) in his/her work, provided
+that the source codes are not distributed. As such, the codes in this file
+will be called by other functions in COPADS but not for direct use by users
+of COPADS - if you intend to call these functions directly, the simplest way
+is to own a copy of both NRP and NRC2.
+
+Copyright (c) Maurice H.T. Ling <mauriceling@acm.org>
+
+Date created: 19th March 2008
+"""
+
+import math
+
+import constants
+from copadsexceptions import FunctionParameterTypeError
+from copadsexceptions import FunctionParameterValueError
+from copadsexceptions import MaxIterationsException
+
+# medfit global data
+ndatat = 0
+xt = []
+yt = []
+aa = 0
+abdevt = 0
+
+# Support Functions
+
+def showvec(x, f='%0.6f'):
+  s = ''
+  for i in x:
+    s = s + str(f%i) + ', '
+  s = s.strip(', ')
+  return '['+s+']'
+
+def nrerror(s):
+  print(s)
+  sys.exit()
+
+def vector(n, value=0.0):
+  a = []
+  for i in range(n+1):
+    a.append(value)
+  return a
+
+def matrix(n, m, value=0.0):
+  a = []
+  for i in range(n+1):
+    a.append(vector(m, value))
+  return a
+
+def SQR(a):
+  sqrarg = a
+  if sqrarg == 0:
+    return 0
+  return sqrarg*sqrarg
+
+def MAX(a, b):
+  if a > b:
+    return a
+  else:
+    return b
+
+def MIN(a, b):
+  if a < b:
+    return a
+  else:
+    return b
+
+def SIGN(a, b):
+  if b >= 0.0:
+    return math.fabs(a)
+  return -math.fabs(a)
+
+# End of Support Functions
+
+def bessi0(x):
+    """
+    Modified Bessel function I-sub-0(x).
+    @see: NRP 6.5
+
+    @param x: float number
+    @return: modified Bessel function base 0 of x
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if abs(x) < 3.75:
+        y = (x/3.75)*(x/3.75)
+        return 1.0 + y * (3.5156229 + y * (3.0899424 + y * (1.2067492 + y * \
+                (0.2659732 + y * (0.360768e-1 + y * 0.45813e-2)))))
+    else:
+        ax = abs(x)
+        y = 3.75/ax
+        return (math.exp(ax)/math.sqrt(ax)) * (0.39894228 + y * \
+            (0.1328592e-1 + \
+             y * (0.225319e-2 + y * (-0.157565e-2 + y * (0.916281e-2 + y * \
+             (-0.2057706e-1 + y * (0.2635537e-1 + y * (-0.1647633e-1 + y * \
+               0.392377e-2))))))))
+
+def bessi1(x):
+    """
+    Bessel function I-sub-1(x).
+    @see: NRP 6.5
+
+    @param x: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if abs(x) < 3.75:
+        y = (x/3.75)*(x/3.75)
+        return x * (0.5 + y * (0.87890594 + y * (0.51498869 + y * \
+            (0.15084934 + \
+               y * (0.2658733e-1 + y * (0.301532e-2 + y * 0.32411e-3))))))
+    else:
+        ax = abs(x)
+        y = 3.75/ax
+        ans = 0.2282967e-1 + y * (-0.2895312e-1 + y * (0.1787654e-1 - y * \
+               0.420059e-2))
+        ans = 0.39894228 + y * (-0.3988024e-1 + y * (-0.362018e-2 + y * \
+                 (0.163801e-1 + y * (-0.1031555e-1 + y * ans))))
+        ans = (math.exp(ax)/math.sqrt(ax))*ans
+        if x < 0.0: return -ans
+        else: return ans
+
+def bessj0(x):
+    """
+    Bessel function J-sub-0(x).
+    @see: NRP 6.4
+
+    @param x: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if abs(x) < 8.0:
+        y = x*x
+        return (57568490574.0 + y * (-13362590354.0 + y * (651619640.7 + \
+               y * (-11214424.18 + y * (77392.33017 + y * \
+                                        (-184.9052456))))))/ \
+               (57568490411.0 + y * (1029532985.0 + y * (9494680.718 + y * \
+               (59272.64853 + y * (267.8532712 + y * 1.0)))))
+    else:
+        ax = abs(x)
+        z = 8.0/ax
+        y = z*z
+        xx = ax - 0.785398164
+        ans1 = 1.0 + y * (-0.1098628627e-2 + y * (0.2734510407e-4 + y * \
+              (-0.2073370639e-5 + y * 0.2093887211e-6)))
+        ans2 = -0.156249995e-1 + y * (0.1430488765e-3 + y * \
+                                      (-0.6911147651e-5 + \
+             y * (0.7621095161e-6 - y * 0.934945152e-7)))
+        return math.sqrt(0.636619772 / ax) * (math.cos(xx) * ans1 - z * \
+               math.sin(xx) * ans2)
+
+def bessj1(x):
+    """
+    Bessel function J-sub-1(x).
+    @see: NRP 6.4
+
+    @param x: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if abs(x) < 8.0:
+        y = x*x
+        ans1 = x * (72362614232.0 + y * (-7895059235.0 + y * \
+                                         (242396853.1 + y * \
+              (-2972611.439 + y * (15704.4826 + y * (-30.16036606))))))
+        ans2 = 144725228442.0 + y * (2300535178.0 + y * (18583304.74 + y * \
+             (99447.43394 + y * (376.9991397 + y))))
+        return ans1 / ans2
+    else:
+        ax = abs(x)
+        x = 8.0 / ax
+        y = z*z
+        xx = ax - 2.356194491
+        ans1 = 1.0 + y * (0.183105e-2 + y * (-0.3516396496e-4 + y * \
+             (0.2457520174e-5 + y * (-0.240337019e-6))))
+        ans2 = 0.04687499995 + y * (-0.2002690873e-3 + y * \
+                                    (0.8449199096e-5 + y * \
+            (-0.88228987e-6 + y * 0.105787412e-6)))
+        if x < 0.0:
+            return math.sqrt(0.636619772 / ax) * \
+                   (math.cos(xx) * ans1 - z * \
+                    math.sin(xx) * ans2)
+        else:
+            return -1 * math.sqrt(0.636619772 / ax) * \
+                   (math.cos(xx) * ans1 - \
+                  z * math.sin(xx) * ans2)
+
+def bessk(n, x):
+    """Bessel function K-sub-n(x). @see: NRP 6.5
+
+    @param n: integer, more than 1 - modified n-th Bessel function
+    @param x: positive integer
+    @return: modified n-th Bessel function of x
+    """
+    if n < 2:
+        raise FunctionParameterValueError('''n must be more than 1 - \
+            use bessk0 or bessk1 for n = 0 or 1 respectively''')
+    else:
+        tox = 2.0/x
+        bkm = bessk0(x)
+        bk = bessk1(x)
+        for j in range(1, n):
+            bkp = bkm * j * tox * bk
+            bkm = bk
+            bk = bkp
+        return bk
+
+def bessk0(x):
+    """Bessel function K-sub-0(x).
+    @see: NRP 6.5
+
+    @param x: positive integer
+    @return: n-th Bessel function of x"""
+    if x <= 2.0:
+        y = x * x/4.0
+        return (-math.log(x/2.0) * bessi0(x)) + (-0.57721566 + y * \
+                (0.4227842 + y * (0.23069756 + y * (0.348859e-1 + y * \
+                (0.262698e-2 + y * (0.1075e-3 + y * 0.74e-5))))))
+    else:
+        y = 2.0/x
+        return (math.exp(-x)/math.sqrt(x)) * (1.25331414 + y * \
+                (-0.7832358e-1 + y * (0.2189568e-1 + y * (-0.1062446e-1 + \
+                y * (0.587872e-2 + y * (-0.25154e-2 + y * 0.53208e-3))))))
+
+def bessk1(x):
+    """
+    Bessel function K-sub-1(x).
+    @see: NRP 6.5
+
+    @param x: positive integer
+    @return: n-th Bessel function of x"""
+    if x <= 2.0:
+        y = x*x/4.0
+        return (math.log(x/2.0) * bessi1(x)) + (1.0/x) * (1.0 + y * \
+                (0.15443144 + y * (-0.67278579 + y * (-0.18156897 + y * \
+                (-0.1919402e-2 + y * (-0.110404e-2 + y * (-0.4686e-4)))))))
+    else:
+        y = 2.0/x
+        return (math.exp(-x)/math.sqrt(x)) * (1.25331414 + y * \
+                (0.23498619 + y * (-0.365562e-1 + y * (0.1504268e-1 + y * \
+                (-0.780353e-2 + y * (0.325614e-2 + y * (-0.68245e-3)))))))
+
+def bessy(n, x):
+    """
+    Bessel function Y-sub-n(x).
+    @see: NRP 6.4
+
+    @param n: integer, more than 1 - n-th Bessel function
+    @param x: positive integer
+    @return: n-th Bessel function of x
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if n < 2:
+        raise FunctionParameterValueError('''n must be more than 1 - \
+            use bessy0 or bessy1 for n = 0 or 1 respectively''')
+    else:
+        tox = 2.0/x
+        by = bessy1(x)
+        bym = bessy0(x)
+        for j in range(1, n):
+            byp = j * tox * by - bym
+            bym = by
+            by = byp
+        return by
+
+def bessy0(x):
+    """
+    Bessel function Y-sub-0(x).
+    @see: NRP 6.4
+    Depend: bessj0
+
+    @param x: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if x < 8.0:
+        y = x*x
+        ans1 = -2957821389.0 + y * (7062834065.0 + y * (-512359803.6 + y * \
+                (10879881.29 + y * (-86327.92757 + y * 228.4622733))))
+        ans2 = 40076544269.0 + y * (745249964.8 + y * (7189466.438 + y * \
+               (47447.2647 + y * (226.1030244 + y * 1.0))))
+        return (ans1 / ans2) + 0.636619772 * bessj0(x) * math.log(x)
+    else:
+        z = 8.0 / x
+        y = z*z
+        xx = x - 0.785398164
+        ans1 = 1.0 + y * (-0.1098628627e-2 + y * (0.2734510407e-4 + y * \
+              (-0.2073370639e-5 + y * 0.2093887211e-6)))
+        ans2 = -0.1562499995e-1 + y * (0.1430488765e-3 + y * \
+                (-0.6911147651e-5 + y * (0.7621095161e-6 + y * \
+                (-0.934945152e-7))))
+        ans = math.sin(xx) * ans1 + z * math.cos(xx) * ans2
+        return math.sqrt(0.636619772 / x) * ans
+
+def bessy1(x):
+    """
+    Bessel function Y-sub-1(x).
+    @see: NRP 6.4
+    Depend: bessj1
+
+    @param x: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if abs(x) < 8.0:
+        y = x*x
+        ans1 = x * (-0.4900604943e13 + y * (0.1275274390e13 + y * \
+                (-0.5153438139e11 + y * (0.7349264551e9 + y * \
+                (-0.4237922726e7 + y * 0.8511937935e4)))))
+        ans2 = 0.2499580570e14 + y * (0.4244419664e12 + y * (0.3733650367e10 + \
+                y * (0.2245904002e8 + y * (0.1020426050e6 + y * \
+                (0.3549632885e3 + y)))))
+        return (ans1/ans2) + 0.636619772 * (bessj1(x) * math.log(x, math.e) - (1.0/x))
+    else:
+        z = 8.0 / x
+        y = z*z
+        xx = x - 2.356194491
+        ans1 = 1.0 + y * (0.183105e-2 + y * (-0.3516396496e-4 + y * \
+             (0.2457520174e-5 + y * (-0.240337019e-6))))
+        ans2 = 0.04687499995 + y * (-0.2002690873e-3 + y * (0.8449199096e-5 + \
+                y * (-0.88228987e-6 + y * 0.105787412e-6)))
+        if x < 0.0: return math.sqrt(0.636619772 / x) * (math.cos(xx) * \
+                            ans1 + z * math.sin(xx) * ans2)
+        else: return math.sqrt(0.636619772 / x) * (math.sin(xx) * \
+                    ans1 + z * math.cos(xx) * ans2)
+
+def beta(z, w):
+    """
+    Beta function.
+    Depend: gammln
+    @see: NRP 6.1
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @param z: float number
+    @param w: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    return math.exp(gammln(z) + gammln(w) - gammln(z+w))
+
+def betacf(a, b, x):
+    """
+    Continued fraction for incomplete beta function.
+    Adapted from salstat_stats.py of SalStat (www.sf.net/projects/salstat)
+    @see: NRP 6.3
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+    """
+    iter_max = 200
+    eps = 3.0e-7
+
+    bm = az = am = 1.0
+    qab = a + b
+    qap = a + 1.0
+    qam = a - 1.0
+    bz = 1.0 - qab * x / qap
+    for i in range(iter_max + 1):
+        em = float(i + 1)
+        tem = em + em
+        d = em * (b - em) * x / ((qam + tem) * (a + tem))
+        ap = az + d * am
+        bp = bz + d * bm
+        d = -(a + em) * (qab + em) * x / ((qap + tem) * (a + tem))
+        app = ap + d * az
+        bpp = bp + d * bz
+        aold = az
+        am = ap / bpp
+        bm = bp / bpp
+        az = app / bpp
+        bz = 1.0
+        if (abs(az - aold) < (eps * abs(az))):
+            return az
+
+def betai(a, b, x):
+    """
+    Incomplete beta function
+
+    I-sub-x(a,b) = 1/B(a,b)*(Integral(0,x) of t^(a-1)(1-t)^(b-1) dt)
+
+    where a,b>0 and B(a,b) = G(a)*G(b)/(G(a+b)) where G(a) is the gamma
+    function of a.
+
+    Adapted from salstat_stats.py of SalStat (www.sf.net/projects/salstat)
+    Depend: betacf, gammln
+    @see: NRP 6.3
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if (x < 0.0 or x > 1.0):
+        raise ValueError('Bad value for x: %s' % x)
+    if (x == 0.0 or x == 1.0):
+        bt = 0.0
+    else:
+        bt = math.exp(gammln(a+b) - gammln(a) - gammln(b) + a *
+                      math.log(x) + b * math.log(1.0-x))
+    if (x < (a + 1.0) / (a + b + 2.0)):
+        return bt * betacf(a, b, x) / float(a)
+    else:
+        return 1.0 - bt * betacf(b, a, 1.0 - x) / float(b)
+
+def bico(n, k):
+    """
+    Binomial coefficient. Returns n!/(k!(n-k)!)
+    Depend: factln, gammln
+    @see: NRP 6.1
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @param n: total number of items
+    @param k: required number of items
+    @return: floating point number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    return math.floor(math.exp(factln(n) - factln(k) - factln(n-k)))
+
+def chebev(a, b, c, m, x):
+    """Chebyshev evaluation.
+    @see: NRP 5.6
+
+    @param a: float number
+    @param b: float number
+    @param c: list of Chebyshev coefficients produced by chebft with the
+    same 'a' and 'b'
+    @param m:
+    @param x:
+    @return: float number - function value
+    """
+    if (x-a)*(x-b) > 0.0:
+        raise FunctionParameterValueError('x must be between a and b')
+    else:
+        d, dd = 0.0, 0.0
+        y = (2.0 * x - a - b) / (b - a)
+        y2 = 2.0 * y
+        for i in range(m, 0, -1):
+            sv = d
+            d = y2 * d - dd + c[i]
+            dd = sv
+        return y * d - dd + 0.5 * c[0]
+
+def covsrt(covar, ma, ia, mfit):
+    '''
+    '''
+    for i in range(mfit+1, ma+1):
+        for j in range(1,i+1):
+            covar[i][j] = 0.0
+            covar[j][i] = 0.0
+    k = mfit
+    for j in range(ma, 0, -1):
+        if (ia[j] == 1):
+            for i in range(1, ma+1):
+                covar[i][k] = covar[i][j]
+                covar[i][j] = covar[i][k]
+            for i in range(1, ma+1):
+                covar[k][i] = covar[j][i]
+                covar[j][i] = covar[k][i]
+            k = k - 1
+    return (covar, ma, ia)
+
+def erf(x):
+    """
+    Error function (a special incomplete gamma function) equivalent to
+    gammp(0.5, x^2) for x => 0. In this routine, gammp is by-passed and gser
+    and gcf are used directly.
+    Depend: gser. gcf, gammln
+    @see: NRP 6.2
+
+    @param x: float number
+    @return: float number
+    """
+    if x < 1.5:
+        return -1*gser(0.5, x)[0]
+    else:
+        return 1.0-gcf(0.5, x)[0]
+
+def erfc(x):
+    """
+    Complementary error function (a special incomplete gamma function)
+    equivalent to gammq(0.5, x^2) which is equivalent to 1 - gammp(0.5, x^2)
+    for x => 0.0
+    Depend: gammp, gammq, gser, gcf, gammln
+    @see: NRP 6.2
+
+    @param x: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if x < 0.0: return 1.0 + gammp(0.5, x*x)
+    else: return gammq(0.5, x*x)
+
+def erfcc(x):
+    """
+    Complementary error function similar to erfc(x) but with fractional error
+    lesser than 1.2e-7.
+    @see: NRP 6.2
+
+    @param x: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    z = abs(x)
+    t = 1.0 / (1.0 + 0.5*z)
+    r = t * math.exp(-z*z-1.26551223+t*(1.00002368+t*(0.37409196+
+        t*(0.09678418+t*(-0.18628806+t*(0.27886807+
+        t*(-1.13520398+t*(1.48851587+t*(-0.82215223+
+        t*0.17087277)))))))))
+    if (x >= 0.0):
+        return r
+    else:
+        return 2.0 - r
+
+def expdev(x):
+    """Depends: ran3
+    @see: NRP 7.2"""
+    return -1.0 * math.log(ran3(x))
+
+def factln(n):
+    """
+    Natural logarithm of factorial: ln(n!)
+    @see: NRP 6.1
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @param n: positive integer
+    @return: natural logarithm of factorial of n
+    """
+    return gammln(n + 1.0)
+
+def fgauss(x, a, y, dyda, na):
+    y = 0.0
+    for i in range(1, na, 3):
+        arg = (x-a[i+1]) / a[i+2]
+        ex = exp(-arg*arg)
+        fac = a[i] * ex * 2.0 * arg
+        y = y + (a[i]*ex)
+        dyda[i] = ex
+        dyda[i+1] = fac / a[i+2]
+        dyda[i+2] = (fac*arg) / a[i+2]
+    return (y, dyda)
+
+def gammln(n):
+    """
+    Complete Gamma function.
+    @see: NRP 6.1
+    @see: http://mail.python.org/pipermail/python-list/2000-June/671838.html
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @param n: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    gammln_cof = [76.18009173, -86.50532033, 24.01409822,
+                  -1.231739516e0, 0.120858003e-2, -0.536382e-5]
+    x = n - 1.0
+    tmp = x + 5.5
+    tmp = (x + 0.5) * math.log(tmp) - tmp
+    ser = 1.0
+    for j in range(6):
+        x = x + 1.
+        ser = ser + gammln_cof[j] / x
+    return tmp + math.log(2.50662827465 * ser)
+
+def gammp(a, x):
+    """
+    Gamma incomplete function, P(a,x).
+    P(a,x) = (1/gammln(a)) * integral(0, x, (e^-t)*(t^(a-1)), dt)
+    Depend: gser, gcf, gammln
+    @see: NRP 6.2
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @param a: float number
+    @param x: float number
+    @return: float number
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if (x < 0.0 or a <= 0.0):
+        raise ValueError('Bad value for a or x: %s, %s' % (a, x))
+    if (x < a + 1.0):
+        return gser(a, x)[0]
+    else:
+        return 1.0 - gcf(a, x)[0]
+
+def gammq(a, x):
+    """
+    Incomplete gamma function: Q(a, x) = 1 - P(a, x) = 1 - gammp(a, x)
+    Also commonly known as Q-equation.
+    @see: http://mail.python.org/pipermail/python-list/2000-June/671838.html
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if (x < 0.0 or a <= 0.0):
+        raise ValueError('Bad value for a or x: %s, %s' % (a, x))
+    if (x < a + 1.0):
+        a = gser(a, x)[0]
+        return 1.0 - a
+    else:
+        return gcf(a, x)[0]
+
+def gcf(a, x, itmax=200, eps=3.e-7):
+    """
+    Continued fraction approx'n of the incomplete gamma function.
+    @see: http://mail.python.org/pipermail/python-list/2000-June/671838.html
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    gln = gammln(a)
+    gold = 0.0
+    a0 = 1.0
+    a1 = x
+    b0 = 0.0
+    b1 = 1.0
+    fac = 1.0
+    n = 1
+    while n <= itmax:
+        an = n
+        ana = an - a
+        a0 = (a1 + a0 * ana) * fac
+        b0 = (b1 + b0 * ana) * fac
+        anf = an * fac
+        a1 = x * a0 + anf * a1
+        b1 = x * b0 + anf * b1
+        if (a1 != 0.0):
+            fac = 1.0 / a1
+            g = b1 * fac
+            if (abs((g - gold) / g) < eps):
+                return (g * math.exp(-x + a * math.log(x) - gln), gln)
+            gold = g
+        n = n + 1
+    raise MaxIterationsException('Maximum iterations reached: %s'
+                                 % abs((g - gold) / g))
+
+def gser(a, x, itmax=700, eps=3.e-7):
+    """
+    Series approximation to the incomplete gamma function.
+    @see: http://mail.python.org/pipermail/python-list/2000-June/671838.html
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    gln = gammln(a)
+    if (x < 0.0):
+        raise ValueError('Bad value for x: %s' % a)
+
+    if (x == 0.0):
+        return(0.0, 0.0)
+    ap = a
+    total = 1.0 / a
+    delta = total
+    n = 1
+    while n <= itmax:
+        ap = ap + 1.0
+        delta = delta * x / ap
+        total = total + delta
+        if (abs(delta) < abs(total) * eps):
+            return (total * math.exp(-x + a * math.log(x) - gln), gln)
+        n = n + 1
+    raise MaxIterationsException('Maximum iterations reached: %s, %s'
+                                 % (abs(delta), abs(total) * eps))
+
+def mdian1(data):
+    """Calculates the median of a list of numerical values using sorting.
+    @see: NRP 13.2
+
+    @param data: a 1-dimensional list of numerical data
+    @return: value of median
+    """
+    data.sort()
+    n2 = len(data) % 2
+    if n2 % 2 == 1: return data[n2+1]
+    else: return 0.5*(data[n2] + data[n2+1])
+
+def mnbrak(ax, bx, func):
+    '''
+    '''
+    GOLD = 1.618034
+    GLIMIT = 100.0
+    TINY = 1.0e-20
+    dum = 0
+    def SHFT(a, b, c, d):
+        return (b, c, d)
+    fa = func(ax)
+    fb = func(bx)
+    if (fb > fa):
+        (dum, ax, bx) = SHFT(dum, ax, bx, dum)
+        (dum, fb, fa) = SHFT(dum, fb, fa, dum)
+    cx = bx + (GOLD * (bx-ax))
+    fc = func(cx)
+    while (fb > fc):
+        r = (bx-ax) * (fb-fc)
+        q = (bx-cx) * (fb-fa)
+        u = bx - \
+            ((((bx-cx) * q) - ((bx-ax) * r)) / \
+            (2.0 * SIGN(FMAX(fabs(q-r), TINY), q-r)))
+        ulim = bx + (GLIMIT * (cx-bx))
+        if ((bx-u) * (u-cx)) > 0.0:
+            fu = func(u)
+            if (fu < fc):
+                ax = bx
+                bx = u
+                fa = fb
+                fb = fu
+                return (ax, bx, cx, fa, fb, fc)
+            elif (fu > fb):
+                cx = u
+                fc = fu
+                return (ax, bx, cx, fa, fb, fc)
+            u = cx + (GOLD * (cx-bx))
+            fu = func(u)
+        elif ((cx-u) * (u-ulim)) > 0.0:
+            fu = func(u)
+            if (fu < fc):
+                (bx, cx, u) = SHFT(bx, cx, u, cx+GOLD*(cx-bx))
+                (fb, fc, fu) = SHFT(fb, fc, fu, func(u))
+        elif ((u-ulim) * (ulim-cx)) >= 0.0:
+            u = ulim
+            fu = func(u)
+        else:
+            u = cx + (GOLD * (cx-bx))
+            fu = func(u)
+        (ax, bx, cx) = SHFT(ax, bx, cx, u)
+        (fa, fb, fc) = SHFT(fa, fb, fc, fu)
+    return (ax, bx, cx, fa, fb, fc)
+
+def moment(data):
+    """Calculates moment from a list of numerical data. @see: NRP 13.1
+
+    @param data: a 1-dimensional list of numerical values
+    @return: (ave, adev, sdev, var, skew, kurt) where
+        - ave = mean
+        - adev = average deviation
+        - sdev = standard deviation
+        - var = variance
+        - skew = skew
+        - kurt = kurtosis
+    """
+    s = 0.0
+    for d in data: s = s + d
+    ave = s/len(data)
+    adev = 0.0
+    svar = 0.0
+    skew = 0.0
+    kurt = 0.0
+    for d in data:
+        s = d - ave
+        adev = adev + abs(s)
+        p = s*s
+        svar = svar + p
+        p = p*s
+        skew = skew + p
+        p = p*s
+        kurt = kurt + p
+    adev = adev/len(data)
+    svar = svar/(len(data) - 1)
+    sdev = math.sqrt(svar)
+    if svar != 0.0:
+        skew = skew/(len(data)*sdev*sdev*sdev)
+        kurt = (kurt/(len(data)*svar*svar)) - 3.0
+    return (ave, adev, sdev, var, skew, kurt)
+
+def pythag(a, b):
+    '''
+    '''
+    a = math.fabs(a)
+    b = math.fabs(b)
+    if (a > b):
+        return a * math.sqrt(1.0 + SQR(b/a))
+    else:
+        if (b == 0.0):
+            return 0.0
+        else:
+            return b * math.sqrt(1.0 + SQR(a/b))
+
+def qgaus(a, b, func):
+    """
+    @see: NRP 4.5"""
+    x = [0.1488743389, 0.4333953941, 0.6794095682, 0.8650633666, 0.97390652]
+    w = [0.2955242247, 0.2692667193, 0.2190863625, 0.1494513491, 0.06667134]
+    xm = 0.5 * (b + a)
+    xr = 0.5 * (b - a)
+    ss = 0.0
+    for i in range(5):
+        dx = xr * x[i]
+        ss = ss + w[i] * (func(xm + dx) + func(xm - dx))
+    return xr * ss
+
+def svdvar(v, ma, w, cvm):
+    '''
+    '''
+    wti = vector(ma)
+    for i in range(1, ma+1):
+        wti[i] = 0.0
+        if (w[i] > 0):
+            wti[i] = 1.0 / (w[i]*w[i])
+    for i in range(1, ma+1):
+        for j in range(1, i+1):
+            sum = 0.0
+        for k in range(1, ma+1):
+            sum = sum + (v[i][k]*v[j][k]*wti[k])
+        cvm[j][i] = sum
+        cvm[i][j] = sum
+    return cvm
+
+#def adi(): raise NotImplementedError
+#def amoeba(): raise NotImplementedError
+#def anneal(): raise NotImplementedError
+#def avevar(): raise NotImplementedError
+#def badluk(): raise NotImplementedError
+#def balanc(): raise NotImplementedError
+#def bcucof(): raise NotImplementedError
+#def bcuint(): raise NotImplementedError
+#def bnldev(): raise NotImplementedError
+#def brent(): raise NotImplementedError
+#def bsstep(): raise NotImplementedError
+#def caldat(): raise NotImplementedError
+#def cel(): raise NotImplementedError
+#def chder(): raise NotImplementedError
+#def chebtf(): raise NotImplementedError
+#def chebpc(): raise NotImplementedError
+#def chint(): raise NotImplementedError
+#def chsone(): raise NotImplementedError
+#def chstwo(): raise NotImplementedError
+#def cntab1(): raise NotImplementedError
+#def cntab2(): raise NotImplementedError
+#def convlv(): raise NotImplementedError
+#def correl(): raise NotImplementedError
+#def cosft(): raise NotImplementedError
+#def dbrent(): raise NotImplementedError
+#def ddpoly(): raise NotImplementedError
+#def des(): raise NotImplementedError
+#def df1dim(): raise NotImplementedError
+#def dfpmin(): raise NotImplementedError
+#def eclass(): raise NotImplementedError
+#def eclazz(): raise NotImplementedError
+#def eigsrt(): raise NotImplementedError
+#def el2(): raise NotImplementedError
+#def elmhes(): raise NotImplementedError
+#def eulsum(): raise NotImplementedError
+#def evlmem(): raise NotImplementedError
+#def f1dim(): raise NotImplementedError
+#def fit(): raise NotImplementedError
+#def fixrts(): raise NotImplementedError
+#def fleg(): raise NotImplementedError
+#def flmoon(): raise NotImplementedError
+#def four1(): raise NotImplementedError
+#def fourn(): raise NotImplementedError
+#def fpoly(): raise NotImplementedError
+#def frprmn(): raise NotImplementedError
+#def ftest(): raise NotImplementedError
+#def gamdev(): raise NotImplementedError
+#def gasdev(): raise NotImplementedError
+#def gauleg(): raise NotImplementedError
+#def gaussj(): raise NotImplementedError
+#def golden(): raise NotImplementedError
+#def hqr(): raise NotImplementedError
+#def hunt(): raise NotImplementedError
+#def indexx(): raise NotImplementedError
+#def irbit1(): raise NotImplementedError
+#def irbit2(): raise NotImplementedError
+#def jacobi(): raise NotImplementedError
+#def julday(): raise NotImplementedError
+#def kendl1(): raise NotImplementedError
+#def kendl2(): raise NotImplementedError
+#def ksone(): raise NotImplementedError
+#def kstwo(): raise NotImplementedError
+#def laguer(): raise NotImplementedError
+#def lfit(): raise NotImplementedError
+#def linmin(): raise NotImplementedError
+#def locate(): raise NotImplementedError
+#def lubksb(): raise NotImplementedError
+#def ludcmp(): raise NotImplementedError
+#def mdian2(): raise NotImplementedError
+#def medfit(): raise NotImplementedError
+#def memcof(): raise NotImplementedError
+#def midexp(): raise NotImplementedError
+#def midinf(): raise NotImplementedError
+#def midpnt(): raise NotImplementedError
+#def midsql(): raise NotImplementedError
+#def midsqu(): raise NotImplementedError
+#def mmid(): raise NotImplementedError
+#def mnbrak(): raise NotImplementedError
+#def mnewt(): raise NotImplementedError
+#def mprove(): raise NotImplementedError
+#def mrqmin(): raise NotImplementedError
+#def odeint(): raise NotImplementedError
+#def pcshft(): raise NotImplementedError
+#def pearsn(): raise NotImplementedError
+#def piksr2(): raise NotImplementedError
+#def piksrt(): raise NotImplementedError
+#def plgndr(): raise NotImplementedError
+#def poidev(): raise NotImplementedError
+#def poicoe(): raise NotImplementedError
+#def polcof(): raise NotImplementedError
+#def poldiv(): raise NotImplementedError
+#def polin2(): raise NotImplementedError
+#def polint(): raise NotImplementedError
+#def powell(): raise NotImplementedError
+#def predic(): raise NotImplementedError
+#def probks(): raise NotImplementedError
+#def pzextr(): raise NotImplementedError
+#def qcksrt(): raise NotImplementedError
+#def qromb(): raise NotImplementedError
+#def qromo(): raise NotImplementedError
+#def qroot(): raise NotImplementedError
+#def qsimp(): raise NotImplementedError
+#def qtrap(): raise NotImplementedError
+#def quad3d(): raise NotImplementedError
+#def ran0(): raise NotImplementedError
+#def ran1(): raise NotImplementedError
+#def ran2(): raise NotImplementedError
+#def ran3(): raise NotImplementedError
+#def ran4(): raise NotImplementedError
+#def rank(): raise NotImplementedError
+#def ratint(): raise NotImplementedError
+#def realft(): raise NotImplementedError
+#def rk4(): raise NotImplementedError
+#def rkdumb(): raise NotImplementedError
+#def rkqc(): raise NotImplementedError
+#def rtbis(): raise NotImplementedError
+#def rtflsp(): raise NotImplementedError
+#def rtnewt(): raise NotImplementedError
+#def rtsafe(): raise NotImplementedError
+#def rtsec(): raise NotImplementedError
+#def rzextr(): raise NotImplementedError
+#def scrsho(): raise NotImplementedError
+#def sfroid(): raise NotImplementedError
+#def shell(): raise NotImplementedError
+#def shoot(): raise NotImplementedError
+#def shootf(): raise NotImplementedError
+#def simplx(): raise NotImplementedError
+#def sinft(): raise NotImplementedError
+#def smooft(): raise NotImplementedError
+#def sncndn(): raise NotImplementedError
+#def solvde(): raise NotImplementedError
+#def sor(): raise NotImplementedError
+#def sort(): raise NotImplementedError
+#def sort2(): raise NotImplementedError
+#def sort3(): raise NotImplementedError
+#def sparse(): raise NotImplementedError
+#def spctrm(): raise NotImplementedError
+#def spear(): raise NotImplementedError
+#def splie2(): raise NotImplementedError
+#def splin2(): raise NotImplementedError
+#def spline(): raise NotImplementedError
+#def splint(): raise NotImplementedError
+#def svbksb(): raise NotImplementedError
+#def svdcmp(): raise NotImplementedError
+#def svdfit(): raise NotImplementedError
+#def toeplz(): raise NotImplementedError
+#def tptest(): raise NotImplementedError
+#def tqli(): raise NotImplementedError
+#def trapzd(): raise NotImplementedError
+#def tred2(): raise NotImplementedError
+#def tridag(): raise NotImplementedError
+#def ttest(): raise NotImplementedError
+#def tutest(): raise NotImplementedError
+#def twofft(): raise NotImplementedError
+#def vander(): raise NotImplementedError
+#def zbrac(): raise NotImplementedError
+#def zbrak(): raise NotImplementedError
+#def zbrent(): raise NotImplementedError
+#def zroots(): raise NotImplementedError
+#def airy(): raise NotImplementedError
+#def amebsa(): raise NotImplementedError
+#def anorm2(): raise NotImplementedError
+#def arcmak(): raise NotImplementedError
+#def arcode(): raise NotImplementedError
+#def arcsum(): raise NotImplementedError
+#def banbks(): raise NotImplementedError
+#def bandec(): raise NotImplementedError
+#def banmul(): raise NotImplementedError
+#def beschb(): raise NotImplementedError
+#def bessik(): raise NotImplementedError
+#def bessjy(): raise NotImplementedError
+#def broydn(): raise NotImplementedError
+#def choldc(): raise NotImplementedError
+#def cholsl(): raise NotImplementedError
+#def cisi(): raise NotImplementedError
+#def cosft1(): raise NotImplementedError
+#def cosft2(): raise NotImplementedError
+#def crank(): raise NotImplementedError
+#def cyclic(): raise NotImplementedError
+#def daub4(): raise NotImplementedError
+#def dawson(): raise NotImplementedError
+#def decchk(): raise NotImplementedError
+#def dfridr(): raise NotImplementedError
+#def dftint(): raise NotImplementedError
+#def ei(): raise NotImplementedError
+#def elle(): raise NotImplementedError
+#def ellf(): raise NotImplementedError
+#def ellpi(): raise NotImplementedError
+#def expint(): raise NotImplementedError
+#def fasper(): raise NotImplementedError
+#def fitexy(): raise NotImplementedError
+#def fourfs(): raise NotImplementedError
+#def fred2(): raise NotImplementedError
+#def fredex(): raise NotImplementedError
+#def fredin(): raise NotImplementedError
+#def frenel(): raise NotImplementedError
+#def gaucof(): raise NotImplementedError
+#def gauher(): raise NotImplementedError
+#def gaujac(): raise NotImplementedError
+#def gaulag(): raise NotImplementedError
+#def hpsel(): raise NotImplementedError
+#def hpsort(): raise NotImplementedError
+#def hufapp(): raise NotImplementedError
+#def hufdec(): raise NotImplementedError
+#def hufenc(): raise NotImplementedError
+#def hufmak(): raise NotImplementedError
+#def hypdrv(): raise NotImplementedError
+#def hypgeo(): raise NotImplementedError
+#def hypser(): raise NotImplementedError
+#def icrc(): raise NotImplementedError
+#def icrc1(): raise NotImplementedError
+#def igray(): raise NotImplementedError
+#def ks2d1s(): raise NotImplementedError
+#def ks2d2s(): raise NotImplementedError
+#def linbcg(): raise NotImplementedError
+#def lnsrch(): raise NotImplementedError
+#def lop(): raise NotImplementedError
+#def machar(): raise NotImplementedError
+#def mgfas(): raise NotImplementedError
+#def mglin(): raise NotImplementedError
+#def miser(): raise NotImplementedError
+#def mp2dfr(): raise NotImplementedError
+#def mpdiv(): raise NotImplementedError
+#def mpinv(): raise NotImplementedError
+#def mppi(): raise NotImplementedError
+#def mrqcof(): raise NotImplementedError
+#def newt(): raise NotImplementedError
+#def orthog(): raise NotImplementedError
+#def pade(): raise NotImplementedError
+#def pccheb(): raise NotImplementedError
+#def period(): raise NotImplementedError
+#def psdes(): raise NotImplementedError
+#def pwt(): raise NotImplementedError
+#def pwtest(): raise NotImplementedError
+#def qrdcmp(): raise NotImplementedError
+#def qrsolv(): raise NotImplementedError
+#def qrupdt(): raise NotImplementedError
+#def quadvl(): raise NotImplementedError
+#def ratlsq(): raise NotImplementedError
+#def ratval(): raise NotImplementedError
+#def rc(): raise NotImplementedError
+#def rd(): raise NotImplementedError
+#def rf(): raise NotImplementedError
+#def rj(): raise NotImplementedError
+#def rkqs(): raise NotImplementedError
+#def rlft3(): raise NotImplementedError
+#def rofunc(): raise NotImplementedError
+#def savgol(): raise NotImplementedError
+#def select(): raise NotImplementedError
+#def selip(): raise NotImplementedError
+#def simpr(): raise NotImplementedError
+#def sobseq(): raise NotImplementedError
+#def sphbes(): raise NotImplementedError
+#def sphfpt(): raise NotImplementedError
+#def sphoot(): raise NotImplementedError
+#def spread(): raise NotImplementedError
+#def sprsax(): raise NotImplementedError
+#def sprsin(): raise NotImplementedError
+#def sprspm(): raise NotImplementedError
+#def sprstm(): raise NotImplementedError
+#def sprstp(): raise NotImplementedError
+#def sprstx(): raise NotImplementedError
+#def stifbs(): raise NotImplementedError
+#def stiff(): raise NotImplementedError
+#def stoerm(): raise NotImplementedError
+#def vegas(): raise NotImplementedError
+#def voltra(): raise NotImplementedError
+#def wt1(): raise NotImplementedError
+#def wtn(): raise NotImplementedError
+#def wwghts(): raise NotImplementedError
+#def zrhqr(): raise NotImplementedError
+#def zriddr(): raise NotImplementedError
+
+def cdf_binomial(k, n, p):
+    """
+    Cummulative density function of Binomial distribution. No reference
+    implementation.
+    Depend: betai, betacf, gammln
+    @see: NRP 6.3
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @param k: number of times of event occurrence in n trials
+    @param n: total number of trials
+    @param p: probability of event occurrence per trial
+    @return: float number - Binomial probability
+    """
+    return betai(k, n - k + 1, p)
+
+def cdf_poisson(k, x):
+    """
+    Cummulative density function of Poisson distribution from 0 to k - 1
+    inclusive. No reference implementation.
+    Depend: gammq, gser, gcf, gammln
+    @see: NRP 6.2
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @param k: number of times of event occurrence
+    @param x: mean of Poisson distribution
+    @return: float number - Poisson probability of k - 1 times of occurrence
+    with the mean of x
+    """
+    return gammq(k, x)
diff --git a/copads/objectdistance.py b/copads/objectdistance.py
new file mode 100644
index 0000000..dc1bdb9
--- /dev/null
+++ b/copads/objectdistance.py
@@ -0,0 +1,933 @@
+"""
+Functions for Calculating Similarity Coefficients between 2 Objects.
+
+Generally, the lower boundary of similar coefficient
+signifies complete difference (no similarity) while the upper boundary
+(if any) signifies complete similarity (no difference).
+
+In the following formulae, the following notations will be used
+    - A = found in both 'original' and 'test'
+    - B = found in 'original' only
+    - C = found in 'test' only
+    - D = not found in either 'original' or 'test'
+    - P = total (that is, P = A + B + C + D)
+
+Copyright (c) Maurice H.T. Ling <mauriceling@acm.org>
+
+Date created: 17th August 2005
+"""
+
+import math
+from copadsexceptions import DistanceInputSizeError
+
+
+def binarize(data, absent=0, type='Full'):
+    """
+    Converts input data in a list of presence or absence of values.
+    For example,
+    binarize([1, 2, 0, 3, 4, 0], 0, 'Full') --> [1, 1, 0, 1, 1, 0]
+    binarize([1, 2, 0, 3, 4, 0], 0, 'Partial') --> [1, 2, 0, 3, 4, 0]
+    binarize([1, 2, 0, 3, 4, 0], 2, 'Full') --> [1, 0, 1, 1, 1, 1]
+    binarize([1, 2, 0, 3, 4, 0], 2, 'Partial') --> [1, 0, 0, 3, 4, 0]
+
+    As 1 and 0 are commonly used to denote presence and absence, please 
+    take care when data contains 1s and 0s.
+
+    @param data: data to binarize
+    @type data: list
+    @param absent: value/symbol to denote absent value. Default = 0.
+    @param type: Denotes type of binarization, which can be 'Full' or 
+    'Partial'. If full binarize, the returned data will only be 1 
+    (denote presence) or 0 (denote absence). If partial binarize, the 
+    returned data will be 0 (denote absence) or the original values 
+    in data (denoting not absent). Default = Full. 
+    """
+    if type == 'Full':
+        return [{absent: 0}.get(x, 1) for x in data]
+    elif type == 'Partial':
+        return [{absent: 0}.get(x, x) for x in data]
+
+def compare(original, test, absent, type='Set'):
+    """
+    Used for processing list-based (positional) or set-based
+    (non-positional) distance of categorical data.
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: indicator to define absent data
+    @param type: {List | Set}
+    """
+    if type == 'Set':
+        original_only = float(len([x for x in original if x not in test]))
+        test_only = float(len([x for x in test if x not in original]))
+        both = float(len([x for x in original if x in test]))
+        return (original_only, test_only, both, 0.0)
+    if type == 'List':
+        original, test = list(original), list(test)
+        original_only, test_only, both, none = 0.0, 0.0, 0.0, 0.0
+        for i in range(len(original)):
+            if original[i] == absent and test[i] == absent:
+                none = none + 1
+            elif original[i] == test[i]:
+                both = both + 1
+            elif original[i] != absent and test[i] == absent:
+                original_only = original_only + 1
+            elif original[i] == absent and test[i] != absent:
+                test_only = test_only + 1
+            else: pass
+        return (original_only, test_only, both, none)
+
+def Jaccard(original, test, absent=0, type='Set'):
+    """
+    Jaccard coefficient for nominal or ordinal data.
+
+    Coefficient: M{A / (A + B + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    return both / (both + original + test)
+
+def Sokal_Michener(original, test, absent=0, type='Set'):
+    """
+    Sokal and Michener coefficient for nominal or ordinal data.
+
+    Coefficient: M{(A + D) / P}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if len(original) != len(test):
+        raise DistanceInputSizeError("Size (length) of inputs must be \
+                equal for Sokal & Michener's distance")
+    (original, test, both, none) = compare(original, test, absent, type)
+    return (both + none) / (original + test + both + none)
+
+def Matching(original, test, absent=0, type='Set'):
+    """
+    Matching coefficient for nominal or ordinal data
+
+    Coefficient: M{(A + D) / (2A + B + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    return (both + none) / (original + both + test + both)
+
+def Dice(original, test, absent=0, type='Set'):
+    """
+    Dice coefficient for nominal or ordinal data.
+
+    Coefficient: M{2A / (2A + B + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    all_region = float(len(original)) + float(len(test))
+    (original, test, both, none) = compare(original, test, absent, type)
+    return (2 * both) / all_region
+
+def Ochiai(original, test, absent=0, type='Set'):
+    """
+    Ochiai coefficient for nominal or ordinal data.
+
+    Coefficient: M{2A / sqrt((A + B)(A + C)))}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    return both / ((both + original) * (both + test)) ** 0.5
+
+def Ochiai2(original, test, absent=0, type='Set'):
+    """
+    Ochiai 2 coefficient for nominal or ordinal data, and requires
+    the presence of regions whereby both original and test are not
+    present.
+
+    Coefficient: M{(A * D) / sqrt((A + B)(A + C)(D + B)(D + C))}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    denominator = ((both + original) * (both + test) * \
+                  (none + original) * (none + test)) ** 0.5
+    return (both * none) / denominator
+
+def Anderberg(original, test, absent=0, type='Set'):
+    """
+    Anderberg coefficient for nominal or ordinal data.
+
+    Coefficient: M{A / (A + 2(B + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    return both / (both + 2 * (original + test))
+
+def Kulczynski2(original, test, absent=0, type='Set'):
+    """
+    Second Kulczynski coefficient for nominal or ordinal data.
+
+    Coefficient: M{((A / (A + B)) + (A / (A + C))) / 2}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    x1 = both / (original + both)
+    x2 = both / (test + both)
+    return (x1 + x2) / 2
+
+def Kulczynski(original, test, absent=0, type='Set'):
+    """
+    First Kulczyski coefficient for nominal or ordinal data.
+
+    Coefficient: M{A / (B + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    return both / (original + test)
+
+def Forbes(original, test, absent=0, type='Set'):
+    """
+    Forbes coefficient for nominal or ordinal data.
+
+    Coefficient: M{A(P) / ((A + B)(A + C))}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    numerator = both * (both + original + test + none)
+    denominator = (original + both) * (test + both)
+    return numerator / denominator
+
+def Hamann(original, test, absent=0, type='Set'):
+    """
+    Hamann coefficient for nominal or ordinal data.
+
+    Coefficient: M{((A + D) - (B + C)) / P}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    numerator = (both + none) - (test + original)
+    return numerator / (original + test + both + none)
+
+def Simpson(original, test, absent=0, type='Set'):
+    """
+    Simpson coefficient for nominal or ordinal data.
+
+    Coefficient: M{A / min(A + B, A + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    return both / min(both + test, both + original)
+
+def Russel_Rao(original, test, absent=0, type='Set'):
+    """
+    Russel and Rao coefficient for nominal or ordinal data.
+
+    Coefficient: M{A / P}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    return both / (original + test + both + none)
+
+def Roger_Tanimoto(original, test, absent=0, type='Set'):
+    """
+    Roger and Tanimoto coefficient for nominal or ordinal data.
+
+    Coefficient: M{(A + D) / (A + 2B + 2C + D)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    denominator = (2 * original) + (2 * test) + both + none
+    return (both + none) / denominator
+
+def Sokal_Sneath(original, test, absent=0, type='Set'):
+    """
+    Sokal and Sneath coefficient for nominal or ordinal data.
+
+    Coefficient: M{A / (A + 2B + 2C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    denominator = (2 * original) + (2 * test) + both
+    return both / denominator
+
+def Sokal_Sneath2(original, test, absent=0, type='Set'):
+    """
+    Sokal and Sneath 2 coefficient for nominal or ordinal data.
+
+    Coefficient: M{(2A + 2D) / (2A + B + C + 2D)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    numerator = (2 * both) + (2 * none)
+    denominator = (2 * both) + original + test + (2 * none)
+    return numerator / denominator
+
+def Sokal_Sneath3(original, test, absent=0, type='Set'):
+    """
+    Sokal and Sneath 3 coefficient for nominal or ordinal data.
+
+    Coefficient: M{(A + D) / (B + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    return (both + none) / (test + original)
+
+def Buser(original, test, absent=0, type='Set'):
+    """
+    Buser coefficient (also known as Baroni-Urbani coefficient)
+    for nominal or ordinal data.
+
+    Coefficient: M{(sqrt(A * D) + A) / (sqrt(A * D) + A + B + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    t = (both + none) ** 0.5
+    return (t + both) / (t + both + test + original)
+
+def Fossum(original, test, absent=0, type='Set'):
+    """
+    Fossum coefficient for nominal or ordinal data.
+
+    Coefficient:
+    M{((A + B + C + D)(A - 0.5)^2) / ((A + B)(A + C))}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    numerator = original + test + both + none
+    numerator = numerator * (both - 0.5) ** 2
+    return numerator / ((both + test) * (both + original))
+
+def YuleQ(original, test, absent=0, type='Set'):
+    """
+    Yule Q coefficient (also known as First Yule coefficient) for
+    nominal or ordinal data.
+
+    Coefficient: M{((A * D) - (B * C)) / ((A * D) + (B * C))}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    numerator = (both * none) - (test * original)
+    denominator = (both * none) + (test * original)
+    return numerator / denominator
+
+def YuleY(original, test, absent=0, type='Set'):
+    """
+    Yule Y coefficient (also known as Second Yule coefficient) for
+    nominal or ordinal data.
+
+    Coefficient:
+    M{(sqrt(A * D) - sqrt(B * C)) / (sqrt(A * D) + sqrt(B * C))}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    numerator = ((both * none) ** 0.5) - ((test * original) ** 0.5)
+    denominator = ((both * none) ** 0.5) + ((test * original) ** 0.5)
+    return numerator / denominator
+
+def Mcconnaughey(original, test, absent=0, type='Set'):
+    """
+    McConnaughey coefficient for nominal or ordinal data.
+
+    Coefficient: M{(A^2 - (B * C)) / (A + B)(A + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    numerator = (both * both) - (original * test)
+    denominator = (both + original) * (both + test)
+    return numerator / denominator
+
+def Stiles(original, test, absent=0, type='Set'):
+    """
+    Stiles coefficient for nominal or ordinal data.
+
+    Coefficient:
+    M{log10((P(|(A * D) - (B * C)| - (P / 2)) ^ 2) /
+    (A + B)(A + C)(B + D)(C + D))}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    n = original + test + both + none
+    t = abs((both * none) - (test * original))
+    numerator = n * ((t - (0.5 * n)) ** 2)
+    denominator = (both + original) * (both + test) * \
+                  (none + original) * (none + test)
+    return math.log10(numerator / denominator)
+
+def Pearson(original, test, absent=0, type='Set'):
+    """
+    Pearson coefficient for nominal or ordinal data.
+
+    Coefficient:
+    M{((A * D) - (B * C)) / (A + B)(A + C)(B + D)(C + D)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    numerator = (both * none) - (test * original)
+    denominator = ((both + original) * (both + test) * \
+                   (none + original) * (none + test)) ** 0.5
+    return numerator / denominator
+
+def Dennis(original, test, absent=0, type='Set'):
+    """
+    Dennis coefficient for nominal or ordinal data.
+
+    Coefficient:
+    M{((A * D) - (B * C)) / P(A + B)(A + C)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    n = original + test + both + none
+    numerator = (both * none) - (test * original)
+    denominator = (n * (both + original) * (both + test)) ** 0.5
+    return numerator / denominator
+
+def Gower_Legendre(original, test, absent=0, type='Set'):
+    """
+    Gower and Legendre coefficient for nominal or ordinal data.
+
+    Coefficient: M{(A + D) / ((0.5 * (B + C)) + A + D)}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    n = original + test + both + none
+    numerator = both + none
+    denominator = (0.5 * (test + original)) + numerator
+    return numerator / denominator
+
+def Tulloss(original, test, absent=0, type='Set'):
+    """
+    Tulloss coefficient for nominal or ordinal data.
+
+    Coefficient:
+    M{sqrt(U * S * R)}
+        - M{U = log(1 + ((min(B, C) + A) / (max(B, C) + A))) / log2}
+        - M{S = 1 / sqrt(log(2 + (min(B, C) / (A + 1))) / log2)}
+        - M{R = log(1 + A / (A + B))log(1 + A / (A + C)) / log2log2}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param absent: user-defined identifier for absent of region,
+        default = 0
+    @param type: {Set | List}, define whether use Set comparison
+        (non-positional) or list comparison (positional), default = Set
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.3
+    """
+    (original, test, both, none) = compare(original, test, absent, type)
+    U = (min(test, original) + both) / (max(test, original) + both)
+    U = math.log10(1 + U) / math.log10(2)
+    S = math.log10(2 + (min(test, original) / (both + 1)))
+    S = 1 / ((S / math.log10(2)) ** 0.5)
+    R = math.log10(1 + (both / (both + original)))
+    R = R * math.log10(1 + (both / (both + test)))
+    R = R / (math.log10(2) * math.log10(2))
+    return (U * S * R) ** 0.5
+
+def Hamming(original, test):
+    """
+    Hamming coefficient for ordinal data - only for positional data.
+
+    Coefficient: number of mismatches with respect to position
+
+    @param original: list of original data
+    @param test: list of data to test against original
+
+    @see: Ling, MHT. 2010. COPADS, I: Distances Measures between Two
+    Lists or Sets. The Python Papers Source Codes 2:2.
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    if len(original) != len(test):
+        raise DistanceInputSizeError("Size (length) of inputs must be \
+            equal for Hamming's distance")
+    mismatch = 0
+    for index in range(len(original)):
+        if original[index] != test[index]: mismatch = mismatch + 1
+    return mismatch
+
+def Euclidean(original, test):
+    """
+    Euclidean coefficient for interval or ratio data.
+
+    Coefficient: M{sqrt(S{sum}(((A + B)(i) - (A + C)(i)) ^ 2))}
+
+    euclidean(original, test) -> euclidean distance between original
+    and test. Adapted from BioPython
+
+    @param original: list of original data
+    @param test: list of data to test against original
+
+    @status: Tested function
+    @since: version 0.1
+    """
+    # lightly modified from implementation by Thomas Sicheritz-Ponten.
+    # This works faster than the Numeric implementation on shorter
+    # vectors.
+    if len(original) != len(test):
+        raise DistanceInputSizeError("Size (length) of inputs must be \
+            equal for Euclidean distance")
+    sum = 0
+    for i in range(len(original)):
+        sum = sum + (original[i] - test[i]) ** 2
+    return math.sqrt(sum)
+
+def Minkowski(original, test, power=3):
+    """
+    Minkowski coefficient for interval or ratio data.
+
+    Coefficient: M{power-th root(S{sum}(((A + B)(i) - (A + C)(i)) ^ power))}
+
+    Minkowski Distance is a generalized absolute form of Euclidean
+    Distance. Minkowski Distance = Euclidean Distance when power = 2
+
+    @param original: list of original data
+    @param test: list of data to test against original
+    @param power: expontential variable
+    @type power: integer
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    if len(original) != len(test):
+        raise DistanceInputSizeError("Size (length) of inputs must be \
+            equal for Minkowski distance")
+    sum = 0
+    for i in range(len(original)):
+        sum = sum + abs(original[i] - test[i]) ** power
+    return sum ** (1 / float(power))
+
+def Manhattan(original, test):
+    """
+    Manhattan coefficient for interval or ratio data.
+
+    Coefficient: M{S{sum}(abs((A + B)(i) - (A + C)(i)))}
+
+    Manhattan Distance is also known as City Block Distance. It is
+    essentially summation of the absolute difference between each
+    element.
+
+    @see: Krause, Eugene F. 1987. Taxicab Geometry. Dover. ISBN 0-486-
+    25202-7.
+
+    @param original: list of original data
+    @param test: list of data to test against original
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    if len(original) != len(test):
+        raise DistanceInputSizeError("Size (length) of inputs must be \
+            equal for Manhattan distance")
+    sum = 0
+    for i in range(len(original)):
+        sum = sum + abs(original[i] - test[i])
+    return float(sum)
+
+def Canberra(original, test):
+    """
+    Canberra coefficient for interval or ratio data.
+
+    Coefficient:
+    M{S{sum}(abs((A + B)(i) - (A + C)(i)) / abs((A + B)(i) + (A + C)(i)))}
+
+    @see: Lance GN and Williams WT. 1966. Computer programs for
+    hierarchical polythetic classification. Computer Journal 9: 60-64.
+
+    @param original: list of original data
+    @param test: list of data to test against original
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    if len(original) != len(test):
+        raise DistanceInputSizeError("Size (length) of inputs must be \
+            equal for Canberra distance")
+    sum = 0
+    for i in range(len(original)):
+        sum = sum + (abs(original[i] - test[i]) / abs(original[i] + \
+            test[i]))
+    return sum
+
+def Bray_Curtis(original, test):
+    """
+    Complement Bray and Curtis coefficient for interval or ratio data.
+    Lower boundary of Bray and Curtis coefficient represents complete
+    similarity (no difference).
+
+    Coefficient:
+    M{1 - S{sum}(abs((A + B)(i) - (A + C)(i))) /
+    (S{sum}((A + B)(i)) + S{sum}((A + C)(i)))}
+
+    @see: Bray JR and Curtis JT. 1957. An ordination of the upland
+    forest communities of S. Winconsin. Ecological Monographs 27:
+    325-349.
+
+    @param original: list of original data
+    @param test: list of data to test against original
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    if len(original) != len(test):
+        raise DistanceInputSizeError("Size (length) of inputs must be \
+            equal for Bray-Curtis distance")
+    return 1 - (Manhattan(original, test) / \
+            float(sum(original) + sum(test)))
+
+def Cosine(original, test):
+    """
+    Cosine coefficient for interval or ratio data.
+
+    Coefficient:
+    M{S{sum}(abs((A + B)(i) * (A + C)(i))) /
+    (S{sum}((A + B) ^ 2) * S{sum}((A + C) ^ 2))}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    if len(original) != len(test):
+        raise DistanceInputSizeError("Size (length) of inputs must be \
+            equal for Cosine distance")
+    original = [float(x) for x in original]
+    test = [float(x) for x in test]
+    numerator = sum([original[x] * test[x] for x in range(len(original))])
+    denominator = sum([x * x for x in original]) ** 0.5
+    denominator = denominator * (sum([x * x for x in test]) ** 0.5)
+    return numerator / denominator
+
+def Tanimoto(original, test):
+    """
+    Tanimoto coefficient for interval or ratio data.
+
+    Coefficient:
+    M{S{sum}(abs((A + B)(i) * (A + C)(i))) /
+    (S{sum}((A + B) ^ 2) + S{sum}((A + C) ^ 2) -
+    S{sum}(abs((A + B)(i) * (A + C)(i))))}
+
+    @param original: list of original data
+    @param test: list of data to test against original
+
+    @status: Tested function
+    @since: version 0.4
+    """
+    if len(original) != len(test):
+        raise DistanceInputSizeError("Size (length) of inputs must be \
+            equal for Cosine distance")
+    original = [float(x) for x in original]
+    test = [float(x) for x in test]
+    numerator = sum([original[x] * test[x] for x in range(len(original))])
+    denominator = sum([x * x for x in original])
+    denominator = denominator + (sum([x * x for x in test])) - numerator
+    return numerator / denominator
diff --git a/copads/ode.py b/copads/ode.py
new file mode 100644
index 0000000..375d871
--- /dev/null
+++ b/copads/ode.py
@@ -0,0 +1,1547 @@
+'''
+Ordinary Differential Equation (ODE) Solvers.
+
+Copyright (c) Maurice H.T. Ling <mauriceling@acm.org>
+
+Date created: 20th December 2014
+'''
+
+def boundary_checker(y, boundary, type):
+    '''
+    Private function - called by ODE solvers to perform boundary checking 
+    of variable values and reset them to specific values if the original 
+    values fall out of the boundary values.
+    
+    Boundary parameter takes the form of a dictionary with variable number 
+    as key and a list of [<boundary value>, <value to set if boundary is 
+    exceeded>]. For example, the following dictionary for lower boundary 
+    (type = 'lower') {'1': [0.0, 0.0], '5': [2.0, 2.0]} will set the lower 
+    boundary of variable y[0] and [5] to 0.0 and 2.0 respectively. This 
+    also allows for setting to a different value - for example, {'1': [0.0, 
+    1.0]} will set variable y[0] to 2.0 if the original y[0] value is 
+    negative.
+    
+    @param y: values for variables
+    @type y: list
+    @param boundary: set of values for boundary of variables
+    @type boundary: dictionary
+    @param type: the type of boundary to be checked, either 'upper' (upper 
+    boundary) or 'lower' (lower boundary)
+    '''
+    for k in list(boundary.keys()):
+        if y[int(k)] < boundary[k][0] and type == 'lower':
+            y[int(k)] = boundary[k][1]
+        if y[int(k)] > boundary[k][0] and type == 'upper':
+            y[int(k)] = boundary[k][1]
+    return y
+
+def Euler(funcs, x0, y0, step, xmax, nonODEfunc=None,
+          lower_bound=None, upper_bound=None,
+          overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y0' = funcs(x0, y0), using 
+    Euler method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        y1 = [0]*n
+        for i in range(n):
+            try: y1[i] = y0[i] + (step*funcs[i](x0, y0))
+            except TypeError: pass
+            except ZeroDivisionError: y0[i] = zerodivision
+            except OverflowError: y0[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+
+def Heun(funcs, x0, y0, step, xmax, nonODEfunc=None,
+         lower_bound=None, upper_bound=None,
+         overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y0' = funcs(x0, y0), using Heun's 
+    method, which is also known as Runge-Kutta 2nd method or Trapezoidal method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1 = [0]*n
+        y1, y2 = [0]*n, [0]*n
+        for i in range(n): 
+            try: f1[i] = funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for i in range(n): 
+            try: y1[i] = y0[i] + step*f1[i]
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y2[i] = overflow
+        for i in range(n): 
+            try: y2[i] = y0[i] + 0.5*step*(f1[i] + funcs[i](x0+step, y1))
+            except TypeError: pass
+            except ZeroDivisionError: y2[i] = zerodivision
+            except OverflowError: y2[i] = overflow
+        return y2
+    while x0 < xmax:
+        y2 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y2 = boundary_checker(y2, lower_bound, 'lower')
+        if upper_bound: 
+            y2 = boundary_checker(y2, upper_bound, 'upper')
+        y0 = y2
+        x0 = x0 + step
+        yield [x0] + y0
+    
+def RK3(funcs, x0, y0, step, xmax, nonODEfunc=None,
+        lower_bound=None, upper_bound=None,
+        overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y0' = funcs(x0, y0), using third
+    order Runge-Kutta method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1, f2, f3 = [0]*n, [0]*n, [0]*n
+        y1, y2 = [0]*n, [0]*n
+        y1 = [0]*n
+        for i in range(n): 
+            try: f1[i] = step * funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for i in range(n): 
+            y1[i] = y0[i] + 0.5*f1[i]
+        for i in range(n): 
+            try: f2[i] = step * funcs[i](x0 + 0.5*step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f2[i] = zerodivision
+            except OverflowError: f2[i] = overflow
+        for i in range(n): 
+            y1[i] = y0[i] - f1[i] + 2*f2[i]
+        for i in range(n): 
+            try: f3[i] = step * funcs[i](x0 + step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f3[i] = zerodivision
+            except OverflowError: f3[i] = overflow
+        for i in range(n): 
+            try: y1[i] = y0[i] + (f1[i] + 4*f2[i] + f3[i])/6.0
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y1[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+        
+def RK4(funcs, x0, y0, step, xmax, nonODEfunc=None,
+        lower_bound=None, upper_bound=None,
+        overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y' = f(x, y), using fourth
+    order Runge-Kutta method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1, f2, f3, f4 = [0]*n, [0]*n, [0]*n, [0]*n
+        y1 = [0]*n
+        for i in range(n):
+            try: f1[i] = funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.5*step*f1[j])
+        for i in range(n):
+            try: f2[i] = funcs[i]((x0+(0.5*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f2[i] = zerodivision
+            except OverflowError: f2[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.5*step*f2[j])
+        for i in range(n):
+            try: f3[i] = funcs[i]((x0+(0.5*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f3[i] = zerodivision
+            except OverflowError: f3[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (step*f3[j])
+        for i in range(n):
+            try: f4[i] = funcs[i]((x0+step), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f4[i] = zerodivision
+            except OverflowError: f4[i] = overflow
+        for i in range(n):
+            try: y1[i] = y0[i] + (step * \
+                    (f1[i] + (2.0*f2[i]) + (2.0*f3[i]) + f4[i]) / 6.0)
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y1[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+        
+def RK38(funcs, x0, y0, step, xmax, nonODEfunc=None,
+        lower_bound=None, upper_bound=None,
+        overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y' = f(x, y), using fourth
+    order Runge-Kutta method, 3/8 rule.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1, f2, f3, f4 = [0]*n, [0]*n, [0]*n, [0]*n
+        y1 = [0]*n
+        for i in range(n):
+            try: f1[i] = funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (step*f1[j]/3.0)
+        for i in range(n):
+            try: f2[i] = funcs[i]((x0+(step/3.0)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f2[i] = zerodivision
+            except OverflowError: f2[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (-1*step*f1[j]/3.0) + (step*f2[j])
+        for i in range(n):
+            try: f3[i] = funcs[i]((x0+(2*step/3.0)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f3[i] = zerodivision
+            except OverflowError: f3[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (step*f1[j]) + (-step*f2[j]) + (step*f3[j])
+        for i in range(n):
+            try: f4[i] = funcs[i]((x0+step), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f4[i] = zerodivision
+            except OverflowError: f4[i] = overflow
+        for i in range(n):
+            try: y1[i] = y0[i] + (step * \
+                    (f1[i] + (3.0*f2[i]) + (3.0*f3[i]) + f4[i]) / 8.0)
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y1[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+
+def CK4(funcs, x0, y0, step, xmax, nonODEfunc=None,
+        lower_bound=None, upper_bound=None,
+        overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y' = f(x, y), using fourth 
+    order Cash-Karp method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1, f2, f3, f4, f5, f6 = [0]*n, [0]*n, [0]*n, [0]*n, [0]*n, [0]*n
+        y1 = [0]*n
+        for i in range(n):
+            try: f1[i] = funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.2*step*f1[j])
+        for i in range(n):
+            try: f2[i] = funcs[i]((x0+(0.2*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f2[i] = zerodivision
+            except OverflowError: f2[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.075*step*f1[j]) + (0.225*step*f2[j])
+        for i in range(n):
+            try: f3[i] = funcs[i]((x0+(0.3*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f3[i] = zerodivision
+            except OverflowError: f3[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.3*step*f1[j]) + (-0.9*step*f2[j]) + \
+                    (1.2*step*f3[j])
+        for i in range(n):
+            try: f4[i] = funcs[i]((x0+(0.6*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f4[i] = zerodivision
+            except OverflowError: f4[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (-11*step*f1[j]/54.0) + (2.5*step*f2[j]) + \
+                    (-70*step*f3[j]/27.0) + (35*step*f4[j]/27.0)
+        for i in range(n):
+            try: f5[i] = funcs[i](x0+step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f5[i] = zerodivision
+            except OverflowError: f5[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (1631*step*f1[j]/55296) + (175*step*f2[j]/512) + \
+                    (575*step*f3[j]/13824) + (44275*step*f4[j]/110592) + \
+                    (253*step*f5[j]/4096)
+        for i in range(n):
+            try: f6[i] = funcs[i](x0+(0.875*step), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f6[i] = zerodivision
+            except OverflowError: f6[i] = overflow
+        for i in range(n):
+            try: y1[i] = y0[i] + (step * \
+                    ((2825*f1[i]/27648) + (18575*f3[i]/48384) + \
+                     (13525*f4[i]/55296) + (277*f5[i]/14336) + \
+                     (0.25*f6[i])))
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y1[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+        
+def CK5(funcs, x0, y0, step, xmax, nonODEfunc=None,
+        lower_bound=None, upper_bound=None,
+        overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y' = f(x, y), using fifth 
+    order Cash-Karp method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1, f2, f3, f4, f5, f6 = [0]*n, [0]*n, [0]*n, [0]*n, [0]*n, [0]*n
+        y1 = [0]*n
+        for i in range(n):
+            try: f1[i] = funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.2*step*f1[j])
+        for i in range(n):
+            try: f2[i] = funcs[i]((x0+(0.2*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f2[i] = zerodivision
+            except OverflowError: f2[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.075*step*f1[j]) + (0.225*step*f2[j])
+        for i in range(n):
+            try: f3[i] = funcs[i]((x0+(0.3*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f3[i] = zerodivision
+            except OverflowError: f3[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.3*step*f1[j]) + (-0.9*step*f2[j]) + \
+                    (1.2*step*f3[j])
+        for i in range(n):
+            try: f4[i] = funcs[i]((x0+(0.6*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f4[i] = zerodivision
+            except OverflowError: f4[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (-11*step*f1[j]/54.0) + (2.5*step*f2[j]) + \
+                    (-70*step*f3[j]/27.0) + (35*step*f4[j]/27.0)
+        for i in range(n):
+            try: f5[i] = funcs[i](x0+step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f5[i] = zerodivision
+            except OverflowError: f5[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (1631*step*f1[j]/55296.0) + \
+                    (175*step*f2[j]/512.0) + (575*step*f3[j]/13824.0) + \
+                    (44275*step*f4[j]/110592.0) + (253*step*f5[j]/4096.0)
+        for i in range(n):
+            try: f6[i] = funcs[i](x0+(0.875*step), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f6[i] = zerodivision
+            except OverflowError: f6[i] = overflow
+        for i in range(n):
+            try: y1[i] = y0[i] + (step * \
+                    ((37*f1[i]/378.0) + (250*f3[i]/621.0) + \
+                     (125*f4[i]/594.0) + (512*f6[i]/1771.0)))
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y1[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+        
+def RKF4(funcs, x0, y0, step, xmax, nonODEfunc=None,
+        lower_bound=None, upper_bound=None,
+        overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y' = f(x, y), using fourth 
+    order Runge-Kutta-Fehlberg method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1, f2, f3, f4, f5, f6 = [0]*n, [0]*n, [0]*n, [0]*n, [0]*n, [0]*n
+        y1 = [0]*n
+        for i in range(n):
+            try: f1[i] = funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.25*step*f1[j])
+        for i in range(n):
+            try: f2[i] = funcs[i]((x0+(0.25*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f2[i] = zerodivision
+            except OverflowError: f2[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (3*step*f1[j]/32.0) + (9*step*f2[j]/32.0)
+        for i in range(n):
+            try: f3[i] = funcs[i]((x0+(3*step/8.0)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f3[i] = zerodivision
+            except OverflowError: f3[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (1932*step*f1[j]/2197.0) + \
+                    (-7200*step*f2[j]/2197.0) + \
+                    (7296*step*f3[j]/2197.0)
+        for i in range(n):
+            try: f4[i] = funcs[i]((x0+(12*step/13.0)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f4[i] = zerodivision
+            except OverflowError: f4[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (439*step*f1[j]/216.0) + (-8.0*step*f2[j]) + \
+                    (3680*step*f3[j]/513.0) + (-845*step*f4[j]/4104.0)
+        for i in range(n):
+            try: f5[i] = funcs[i](x0+step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f5[i] = zerodivision
+            except OverflowError: f5[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (-8*step*f1[j]/27.0) + (2.0*step*f2[j]) + \
+                    (-3544*step*f3[j]/2565.0) + (1859*step*f4[j]/4104.0) + \
+                    (-11*step*f5[j]/40.0)
+        for i in range(n):
+            try: f6[i] = funcs[i](x0+(0.5*step), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f6[i] = zerodivision
+            except OverflowError: f6[i] = overflow
+        for i in range(n):
+            try: y1[i] = y0[i] + (step * \
+                    ((25*f1[i]/216.0) + (1408*f3[i]/2565.0) + \
+                     (2197*f4[i]/4104.0) + (-0.2*f5[i])))
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y1[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+      
+def RKF5(funcs, x0, y0, step, xmax, nonODEfunc=None,
+        lower_bound=None, upper_bound=None,
+        overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y' = f(x, y), using fifth 
+    order Runge-Kutta-Fehlberg method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1, f2, f3, f4, f5, f6 = [0]*n, [0]*n, [0]*n, [0]*n, [0]*n, [0]*n
+        y1 = [0]*n
+        for i in range(n):
+            try: f1[i] = funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.25*step*f1[j])
+        for i in range(n):
+            try: f2[i] = funcs[i]((x0+(0.25*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f2[i] = zerodivision
+            except OverflowError: f2[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (3*step*f1[j]/32.0) + (9*step*f2[j]/32.0)
+        for i in range(n):
+            try: f3[i] = funcs[i]((x0+(3*step/8.0)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f3[i] = zerodivision
+            except OverflowError: f3[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (1932*step*f1[j]/2197.0) + \
+                    (-7200*step*f2[j]/2197.0) + \
+                    (7296*step*f3[j]/2197.0)
+        for i in range(n):
+            try: f4[i] = funcs[i]((x0+(12*step/13.0)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f4[i] = zerodivision
+            except OverflowError: f4[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (439*step*f1[j]/216.0) + \
+                    (-8.0*step*f2[j]) + (3680*step*f3[j]/513.0) + \
+                    (-845*step*f4[j]/4104.0)
+        for i in range(n):
+            try: f5[i] = funcs[i](x0+step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f5[i] = zerodivision
+            except OverflowError: f5[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (-8*step*f1[j]/27.0) + (2.0*step*f2[j]) + \
+                    (-3544*step*f3[j]/2565.0) + (1859*step*f4[j]/4104.0) + \
+                    (-11*step*f5[j]/40.0)
+        for i in range(n):
+            try: f6[i] = funcs[i](x0+(0.5*step), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f6[i] = zerodivision
+            except OverflowError: f6[i] = overflow
+        for i in range(n):
+            try: y1[i] = y0[i] + (step * \
+                    ((16*f1[i]/135.0) + (6656*f3[i]/12825.0) + \
+                     (28561*f4[i]/56430.0) + (-9*f5[i]/50) + \
+                     (2*f6[i]/55)))
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y1[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+        
+def DP4(funcs, x0, y0, step, xmax, nonODEfunc=None,
+        lower_bound=None, upper_bound=None,
+        overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y' = f(x, y), using fourth 
+    order Dormand-Prince method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1, f2, f3 = [0]*n, [0]*n, [0]*n
+        f4, f5, f6, f7 = [0]*n, [0]*n, [0]*n, [0]*n
+        y1 = [0]*n
+        for i in range(n):
+            try: f1[i] = funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.2*step*f1[j])
+        for i in range(n):
+            try: f2[i] = funcs[i]((x0+(0.2*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f2[i] = zerodivision
+            except OverflowError: f2[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (3*step*f1[j]/40.0) + (9*step*f2[j]/40.0)
+        for i in range(n):
+            try: f3[i] = funcs[i]((x0+(0.3*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f3[i] = zerodivision
+            except OverflowError: f3[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (44*step*f1[j]/45.0) + (-56*step*f2[j]/15.0) + \
+                    (32*step*f3[j]/9.0)
+        for i in range(n):
+            try: f4[i] = funcs[i]((x0+(0.8*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f4[i] = zerodivision
+            except OverflowError: f4[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (19372*step*f1[j]/6561.0) + \
+                    (-25360*step*f2[j]/2187.0) + \
+                    (64448*step*f3[j]/6561.0) + \
+                    (-212*step*f4[j]/729.0)
+        for i in range(n):
+            try: f5[i] = funcs[i](x0+(8*step/9.0), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f5[i] = zerodivision
+            except OverflowError: f5[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (9017*step*f1[j]/3168.0) + \
+                    (-355*step*f2[j]/33.0) + (46732*step*f3[j]/5247.0) + \
+                    (49*step*f4[j]/176.0) + (-5103*step*f5[j]/18656.0)
+        for i in range(n):
+            try: f6[i] = funcs[i](x0+step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f6[i] = zerodivision
+            except OverflowError: f6[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (35*step*f1[j]/384.0) + \
+                    (500*step*f3[j]/1113.0) + (125*step*f4[j]/192.0) + \
+                    (-2187*step*f5[j]/6784.0) + (11*step*f6[j]/84.0)
+        for i in range(n):
+            try: f7[i] = funcs[i](x0+step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f7[i] = zerodivision
+            except OverflowError: f7[i] = overflow
+        for i in range(n):
+            try: y1[i] = y0[i] + (step * \
+                    ((5179*f1[i]/57600.0) + (7571*f3[i]/16695.0) + \
+                     (393*f4[i]/640.0) + (-92097*f5[i]/339200.0) + \
+                     (187*f6[i]/2100.0) + (f7[i]/40.0)))
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y1[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+        
+def DP5(funcs, x0, y0, step, xmax, nonODEfunc=None,
+        lower_bound=None, upper_bound=None,
+        overflow=1e100, zerodivision=1e100):
+    '''
+    Generator to integrate a system of ODEs, y' = f(x, y), using fifth 
+    order Dormand-Prince method.
+    
+    A function (as nonODEfunc parameter) can be included to modify one or 
+    more variables (y0 list). This function will not be an ODE (not a 
+    dy/dt). This can be used to consolidate the modification of one or 
+    more variables at each ODE solving step. For example, y[0] = y[1] / y[2] 
+    can be written as 
+    
+    >>> def modifying_function(y, step):
+            y[0] = y[1] / y[2]
+            return y
+    
+    This function must take 'y' (variable list) and 'step' (time step) as 
+    parameters and must return 'y' (the modified variable list). This 
+    function will execute before boundary checking at each time step.
+    
+    Upper and lower boundaries of one or more variable can be set using 
+    upper_bound and lower_bound parameters respectively. These parameters 
+    takes the form of a dictionary with variable number as key and a list 
+    of [<boundary value>, <value to set if boundary is exceeded>]. For 
+    example, the following dictionary for lower boundary {'1': [0.0, 0.0], 
+    '5': [2.0, 2.0]} will set the lower boundary of variable y[0] and y[5] 
+    to 0.0 and 2.0 respectively. This also allows for setting to a different 
+    value - for example, {'1': [0.0, 1.0]} will set variable y[0] to 2.0 if 
+    the original y[0] value is negative.
+    
+    @param funcs: system of differential equations
+    @type funcs: list
+    @param x0: initial value of x-axis, which is usually starting time
+    @type x0: float
+    @param y0: initial values for variables
+    @type y0: list
+    @param step: step size on the x-axis (also known as step in calculus)
+    @type step: float
+    @param xmax: maximum value of x-axis, which is usually ending time
+    @type xmax: float
+    @param nonODEfunc: a function to modify the variable list (y0)
+    @type nonODEfunc: function
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    '''
+    yield [x0] + y0
+    def solver(funcs, x0, y0, step):
+        n = len(funcs)
+        f1, f2, f3 = [0]*n, [0]*n, [0]*n
+        f4, f5, f6, f7 = [0]*n, [0]*n, [0]*n, [0]*n
+        y1 = [0]*n
+        for i in range(n):
+            try: f1[i] = funcs[i](x0, y0)
+            except TypeError: pass
+            except ZeroDivisionError: f1[i] = zerodivision
+            except OverflowError: f1[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (0.2*step*f1[j])
+        for i in range(n):
+            try: f2[i] = funcs[i]((x0+(0.2*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f2[i] = zerodivision
+            except OverflowError: f2[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (3*step*f1[j]/40.0) + (9*step*f2[j]/40.0)
+        for i in range(n):
+            try: f3[i] = funcs[i]((x0+(0.3*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f3[i] = zerodivision
+            except OverflowError: f3[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (44*step*f1[j]/45.0) + (-56*step*f2[j]/15.0) + \
+                    (32*step*f3[j]/9.0)
+        for i in range(n):
+            try: f4[i] = funcs[i]((x0+(0.8*step)), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f4[i] = zerodivision
+            except OverflowError: f4[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (19372*step*f1[j]/6561.0) + \
+                    (-25360*step*f2[j]/2187.0) + \
+                    (64448*step*f3[j]/6561.0) + \
+                    (-212*step*f4[j]/729.0)
+        for i in range(n):
+            try: f5[i] = funcs[i](x0+(8*step/9.0), y1)
+            except TypeError: pass
+            except ZeroDivisionError: f5[i] = zerodivision
+            except OverflowError: f5[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (9017*step*f1[j]/3168.0) + \
+                    (-355*step*f2[j]/33.0) + (46732*step*f3[j]/5247.0) + \
+                    (49*step*f4[j]/176.0) + (-5103*step*f5[j]/18656.0)
+        for i in range(n):
+            try: f6[i] = funcs[i](x0+step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f6[i] = zerodivision
+            except OverflowError: f6[i] = overflow
+        for j in range(n):
+            y1[j] = y0[j] + (35*step*f1[j]/384.0) + \
+                    (500*step*f3[j]/1113.0) + (125*step*f4[j]/192.0) + \
+                    (-2187*step*f5[j]/6784.0) + (11*step*f6[j]/84.0)
+        for i in range(n):
+            try: f7[i] = funcs[i](x0+step, y1)
+            except TypeError: pass
+            except ZeroDivisionError: f7[i] = zerodivision
+            except OverflowError: f7[i] = overflow
+        for i in range(n):
+            try: y1[i] = y0[i] + (step * \
+                    ((35*f1[i]/384.0) + (500*f3[i]/1113.0) + \
+                     (125*f4[i]/192.0) + (-2187*f5[i]/6784.0) + \
+                     (11*f6[i]/84.0)))
+            except TypeError: pass
+            except ZeroDivisionError: y1[i] = zerodivision
+            except OverflowError: y1[i] = overflow
+        return y1
+    while x0 < xmax:
+        y1 = solver(funcs, x0, y0, step)
+        if nonODEfunc:
+            y1 = nonODEfunc(y1, step)
+        if lower_bound: 
+            y1 = boundary_checker(y1, lower_bound, 'lower')
+        if upper_bound: 
+            y1 = boundary_checker(y1, upper_bound, 'upper')
+        y0 = y1
+        x0 = x0 + step
+        yield [x0] + y0
+
+def _equation_constructor(expressions={},
+                          parameters={},
+                          variables=[]):
+    '''
+    Private function to support ODE_constructor to generate ODE function 
+    for each ODE.
+    
+    @param expressions: dictionary of expressions for ODE(s). Please see 
+    above documentation. 
+    @param parameters: dictionary of parameter values to be substituted 
+    into the ODE equations
+    @param variables: list of additional variables to be tagged for 
+    substitution(s)
+    @return: tuple of (<list of generated ODE functions>, <list of tagged 
+    variables>) 
+    '''
+    statements = []
+    # Generate ODE function, one at a time
+    for name in expressions.keys():
+        # Generate ODE function definition
+        stmt = '\ndef %s(t, y):' % str(name)
+        expression = expressions[name]
+        if type(expression) == type(''): expression = [expression]
+        count = 1
+        # Generate list of expression(s) for current ODE function
+        exp_list = []
+        variables = list(set(list(variables) + list(expressions.keys())))
+        for exp in expression:
+            # Substitute parameter/variable values 
+            for k in parameters.keys():
+                exp = exp.replace(str(k), str(parameters[k]))
+            #variables = list(set(variables + expressions.keys()))
+            
+            for v in variables:
+                exp = exp.replace(str(v), 'v@'+str(v))
+            # Generate expression codes
+            stmt = stmt + '\n    exp_%s = %s' % (str(count), str(exp))
+            exp_list.append('exp_%s' % str(count))
+            count = count + 1
+        # Compile generated expressions into return value
+        return_stmt = '\n    return ' + ' + '.join(exp_list)
+        stmt = stmt + return_stmt
+        statements.append(stmt)
+    return (statements, variables)
+
+def _modifying_constructor(modifying_expressions):
+    '''
+    Private function to support ODE_constructor to generate function code 
+    for modifying expressions.
+    
+    @param modifying_expressions: list of expressions to modify the 
+    variables
+    @return: generated function code
+    '''
+    stmt = '\ndef modifying_expression(y, step):'
+    for exp in modifying_expressions:
+        stmt = stmt + '\n    %s' % str(exp)
+    stmt = stmt + '\n    return y'
+    return stmt
+
+def ODE_constructor(scriptfile,
+                    resultsfile,
+                    time=(0.0, 0.1, 100.0),
+                    ODE_solver='RK4',
+                    expressions={},
+                    parameters={},
+                    initial_conditions={},
+                    modifying_expressions=[],
+                    lower_bound=None, 
+                    upper_bound=None,
+                    overflow=1e100, 
+                    zerodivision=1e100):
+    '''
+    Function to construct an ODE simulation script file from given 
+    definitions.
+    
+    For example, the following system of ODEs::
+    
+        d(human)/dt = birth - zombied - death
+        d(zombie)/dt = zombied + resurrected - destroyed
+        d(dead)/dt = death + destroyed - resurrected
+        
+        birth = 0
+        zombied = 0.0095 * human * zombie
+        death = 0.0001 * human
+        resurrected = 0.0002 * dead
+        destroyed = 0.0003 * human * zombie
+         
+        where initially (t0), 
+         
+        number of humans = 500
+        number of zombies = 0
+        number of dead = 0
+         
+        and with the following boundaries
+         
+        number of humans > 0
+        number of zombies < 10000
+        number of dead < 10000
+         
+        and there is an influx of 5 humans per day into the infected village
+    
+    can be specified as 
+    
+    >>> scriptfile = 'zombie_attack.py'
+        resultsfile = 'zombie_data.csv'
+        time = (0.0, 0.1, 100.0)
+        ODE_solver = 'RK4'
+        expressions = {'human': ['birth_rate',
+                                 '- (transmission_rate * human * zombie)',
+                                 '- (death_rate * human)'],
+                       'zombie': ['(transmission_rate * human * zombie)',
+                                  '(resurrection_rate * dead)',
+                                  '- (destroy_rate * human * zombie)'],
+                       'dead': ['(death_rate * human)',
+                                '(destroy_rate * human * zombie)',
+                                '- (resurrection_rate * dead)']}
+        parameters = {'birth_rate': 0.0,           # birth rate
+                      'transmission_rate': 0.0095, # transmission percent   
+                      'death_rate': 0.0001,        # natural death percent  
+                      'resurrection_rate': 0.0002, # resurect percent  
+                      'destroy_rate':0.0003        # destroy percent   
+                      }
+        initial_conditions = {'human': 500.0,
+                              'zombie': 0.0, 
+                              'dead': 0.0}
+        modifying_expression = ['human = human + (5 * step)']
+        lower_bound = {'human': [0.0, 0.0]}
+        upper_bound = {'zombie': [10000.0, 10000.0],
+                       'dead': [10000.0, 10000.0]}
+        overflow = 1e100
+        zerodivision = 1e100
+    
+    @param scriptfile: name of Python file for the generated ODE script 
+    file
+    @type scriptfile: string
+    @param resultsfile: name of ODE simulation results file (CSV file), 
+    which will be included into the generated simulation script file
+    @type resultsfile: string    
+    @param time: tuple of time parameters for simulation in the format of 
+    (<start time>, <time step>, <end time>). Default = (0.0, 0.1, 100.0)
+    @param ODE_solver: name of ODE solver to use. Default = RK4
+    @param expressions: dictionary of expressions for ODE(s). Please see 
+    above documentation. 
+    @param parameters: dictionary of parameter values to be substituted 
+    into the ODE equations
+    @param modifying_expressions: list of expressions to modify the 
+    variables
+    @param initial_conditions: dictionary of initial conditions for each 
+    ODE
+    @param lower_bound: set of values for lower boundary of variables
+    @type lower_bound: dictionary
+    @param upper_bound: set of values for upper boundary of variables
+    @type upper_bound: dictionary
+    @param overflow: value (usually a large value) to assign in event of 
+    over flow error (usually caused by a large number) during integration. 
+    Default = 1e100.
+    @type overflow: float
+    @param zerodivision: value (usually a large value) to assign in event 
+    of zero division error, which results in positive infinity, during 
+    integration. Default = 1e100.
+    @type zerodivision: float
+    @return: generated ODE codes for the entire script
+    @rtype: list
+    '''
+    statements = [] 
+    initial_conditions_list = initial_conditions.keys()
+    # Construct ODE functions
+    (ODE_functions,
+     variables) = _equation_constructor(expressions,
+                                        parameters,
+                                        initial_conditions_list)
+    # Construct modifying expression
+    mexpression = _modifying_constructor(modifying_expressions)
+    # Generate ODE functions/equations table
+    table = {}
+    count = 0
+    for v in initial_conditions_list:
+        table[str(v)] = str(count)
+        count = count + 1
+    # Generate lower boundary table
+    if lower_bound != None:
+        lbound = {}
+        for k in lower_bound.keys():
+            lbound[table[k]] = lower_bound[k]
+    else:
+        lbound = None
+    # Generate upper boundary table
+    if upper_bound != None:
+        ubound = {}
+        for k in upper_bound.keys():
+            ubound[table[k]] = upper_bound[k]
+    else:
+        ubound = None
+    # Write ode module import
+    statements.append('import ode\n\n')  
+    # Generate variable array and statements
+    statements.append('y = range(%s)\n' % str(len(variables)))
+    count = 0
+    for k in initial_conditions_list:
+        statements.append('y[%s] = %s\n' % \
+                          (str(count), str(initial_conditions[k])))
+        count = count + 1
+    statements.append('\n')
+    # Perform variable replacements in ODE functions, and write functions
+    for funct in ODE_functions:
+        for v in table.keys():
+            funct = funct.replace('v@'+v, 'y[%s]' % table[v])
+        statements.append(funct)
+    statements.append('\n\n')
+    # Perform variable replacements in modifying expression, and write out
+    for v in table.keys():
+        mexpression = mexpression.replace(v, 'y[%s]' % table[v])
+    statements.append(mexpression)
+    statements.append('\n\n')
+    # Generate ODE assignment array and statements
+    statements.append('ODE = range(%s)\n' % str(len(variables)))
+    count = 0
+    for k in initial_conditions_list:
+        statements.append('ODE[%s] = %s\n' % (str(count), str(k)))
+        count = count + 1
+    statements.append('\n')
+    # Generate ODE execution codes
+    statements.append('stime = %s \n' % str(time[0]))
+    statements.append('step = %s \n' % str(time[1]))
+    statements.append('etime = %s \n' % str(time[2]))
+    statements.append('lower_bound = %s \n' % str(lbound))
+    statements.append('upper_bound = %s \n' % str(ubound))
+    statements.append('overflow = %s \n' % str(overflow))
+    statements.append('zerodivision = %s \n' % str(zerodivision))
+    statements.append('\n')
+    statements.append("f = open('%s', 'w')\n" % str(resultsfile))
+    headers = ['time'] + [str(k)for k in initial_conditions.keys()]
+    headers = ','.join(headers)
+    statements.append("f.write('%s' + '\\n') \n" % headers)
+    statements.append('\n')
+    statements.append('for x in ode.%s(ODE, stime, y, step, etime, \n' % \
+                      str(ODE_solver))
+    statements.append('        modifying_expression, \n') 
+    statements.append('        lower_bound, upper_bound, \n') 
+    statements.append('        overflow, zerodivision): \n')
+                      
+    statements.append("    f.write(','.join([str(item) for item in x]) + '\\n')")
+    statements.append('\n')
+    statements.append('f.close()')
+    # Write generated codes into script file
+    sfile = open(scriptfile, 'w')
+    sfile.writelines(statements)
+    sfile.close()
+    return statements
+    
\ No newline at end of file
diff --git a/copads/pnet.py b/copads/pnet.py
new file mode 100644
index 0000000..e7f3d7c
--- /dev/null
+++ b/copads/pnet.py
@@ -0,0 +1,551 @@
+'''!
+Framework for Petri Nets Typed Applications
+
+Date created: 3rd January 2016
+
+License: Python Software Foundation License version 2
+'''
+
+class Place(object):
+    '''!
+    Class to represent a place or container in petri nets. The tokens 
+    are represented as a dictionary where each token is represented as 
+    a key-value pair. The key represents the type of token and the 
+    value represents the number of such tokens. This enables more than 
+    one type of tokens to be represented.
+    '''
+    def __init__(self, name):
+        '''!
+        Contructor method.
+        
+        @param name string: name of the place/container
+        '''
+        self.name = str(name)
+        self.attributes = {}
+        
+class PNet(object):
+    '''!
+    Class to represent a Petri Net or Petri Net typed object.
+    
+    The places and transition rules are represented as dictionary 
+    objects. Places dictionary will have the name of place as key and 
+    the Place (pnet.Place object) as value. Transition rules 
+    dictionary will have the name of rule appended with a number (in 
+    the format of <rule name>_<number> in order to ensure uniqueness) 
+    as key and the value is a dictionary to represent the transition 
+    rule (the structure of the transition rule dictionary is dependent 
+    on the type of rules).
+    
+    The following types of transition rules are allowed: 
+        - step rule
+        - delay rule
+        - incubate rule
+        - ratio rule
+        - function rule
+        
+    Step rule is to be executed at each time step. For example, if 20g 
+    of flour is to be transferred from flour bowl to mixer bowl at 
+    each time step, this 'add_flour' rule can be defined as:
+    
+    >>> net.add_rules('add_flour', 'step', ['flour.flour -> mixer.flour; 20'])
+    
+    A single step rule can trigger more than one token movement. For 
+    example, the following step rule simulates the mixing of 
+    ingredients into a flour dough:
+    
+    >>> net.add_rules('blend', 'step', ['mixer.flour -> mixer.dough; 15', 'mixer.water -> mixer.dough; 10', 'mixer.sugar -> mixer.dough; 0.9', 'mixer.yeast -> mixer.dough; 1'])
+                       
+    Delay rule acts as a time delay between each token movement. For 
+    example, the following rule simulates the transfer of 0.5g of 
+    yeast into the mixer bowl:
+    
+    >>> net.add_rules('add_yeast', 'delay', ['yeast.yeast -> mixer.yeast; 0.5; 10'])
+                      
+    Incubate rule is a variation of delay rule. While delay rule is 
+    not condition dependent, incubate rule starts a time delay when 
+    one or more conditions are met. For example,
+    
+    >>> net.add_rules('rise', 'incubate', ['10; mixer.dough -> pan.dough; mixer.flour == 0; mixer.water == 0; mixer.sugar == 0; mixer.yeast == 0'])
+                       
+    sets a 10 time step delay when all flour, water, sugar, and yeast 
+    in the mixer bowl are used up, which simulates the completemixing 
+    into a bread dough. The 10 time step then simulates the time 
+    needed for the dough to rise. After 10 time steps, dough in the 
+    mixer is transferred into the pan.
+    
+    Ratio rule is a variant of step rule. Instead of absolute number 
+    of tokens to move, the movement is a percentage of the number of 
+    tokens. For example, 
+    
+    >>> net.add_rules('bake', 'ratio', ['pan.dough -> pan.bread; 0.3; pan.dough < 1; 0'])
+                      
+    will move 30% of the token value from dough in pan to bread in 
+    pan. If the token value of dough in pan is less than 1, then the 
+    token value of dough in pan will be set to 0.
+    
+    Function rule is a generic and free-form rule, that takes the form 
+    of a Python function. This is usually used when the transition 
+    cannot be represented by any other rules. Given a user-defined 
+    function, FUNC, 
+    
+    >>> net.add_rules('cool', 'function', ['table.temperature -> air.temperature', FUNC, 'table.bread > 0; table.temperature > 30'])
+                       
+    FUNC will be executed when table.bread > 0 and table.temperature > 
+    30. The returned result of FUNC will be the token transfer from 
+    table.temperature to air.temperature. 
+    
+    FUNC takes all the places as a single parameter, such as FUNC(
+    places), where 'places' is a dictionary with the name of each 
+    place as key. Token values in any place can be assessed. For 
+    example,
+    
+    >>> place_names = places.keys()
+
+    >>> a_place = places[places_names[0]]
+    
+    >>> token_set = a_place.attributes.keys()
+    
+    >>> a_token_value = a_place.attributes[token_set[0]]
+    '''
+    def __init__(self, zerolowerbound=True):
+        '''!
+        Contructor method.
+        
+        @param zerolowerbound boolean: flag to determine whether 
+        number of tokens is bounded at zero. Default = True (the 
+        lowest number for tokens is zero)
+        '''
+        self.places = {}
+        self.add_places('ouroboros', {'U': float('inf')})
+        self.rules = {}
+        self.report = {}
+        self.losses = {}
+        self.zerolowerbound = zerolowerbound
+        self.rulenumber = 1
+    
+    def add_places(self, place_name, tokens):
+        '''!
+        Method to add a place/container into the Petri Net.
+        
+        For example, the following adds a "flour" place containing 
+        1000 tokens of flour, which can be seen as a bowl of 1000g of 
+        flour:
+        
+        >>> net.add_places('flour', {'flour': 1000})
+        
+        @param place_name string: name of the place/container
+        @param tokens dictionary: token(s) for the place/container 
+        where key is the name/type of token and value is the number of 
+        tokens for the specific type
+        '''
+        self.places[place_name] = Place(place_name)
+        self.places[place_name].attributes = tokens
+    
+    def add_rules(self, rule_name, rule_type, actions):
+        '''!
+        Method to add a transition rule into the Petri Net.
+        
+        @param rule_name string: name of the transition rule. This 
+        name need not be unique within the model as this method will 
+        append a running rule number to the name to ensure internal 
+        uniqueness.
+        @param rule_type string: type of rule. Allowable types are 
+        'step' for step rule, 'delay' for delay rule, and 'incubate' 
+        for incubate rule. Please see module documentation for the 
+        description of rules.
+        @param actions list: describe the action(s) of the transition 
+        rule
+        '''
+        if rule_type not in ['function']:
+            for t in actions:
+                t = [x.strip() for x in t.split(';')]
+                d = {'type': rule_type,
+                     'movement': None}
+                if rule_type == 'step': 
+                    movement = [x.strip() for x in t[0].split('->')]
+                    d['movement'] = [(loc.split('.')[0], loc.split('.')[1]) 
+                                     for loc in movement]
+                    d['value'] = float(t[1])
+                if rule_type == 'delay': 
+                    movement = [x.strip() for x in t[0].split('->')]
+                    d['movement'] = [(loc.split('.')[0], loc.split('.')[1]) 
+                                     for loc in movement]
+                    d['value'] = float(t[1])
+                    d['delay'] = int(t[2])
+                if rule_type == 'incubate':
+                    d['value'] = float(t[0])
+                    movement = [x.strip() for x in t[1].split('->')]
+                    d['movement'] = [(loc.split('.')[0], loc.split('.')[1]) 
+                                     for loc in movement]
+                    d['conditions'] = [cond for cond in t[2:]]
+                    d['timer'] = 0
+                if rule_type == 'ratio':
+                    movement = [x.strip() for x in t[0].split('->')]
+                    d['movement'] = [(loc.split('.')[0], loc.split('.')[1]) 
+                                     for loc in movement]
+                    d['ratio'] = float(t[1])
+                    d['limit_check'] = t[2]
+                    d['limit_set'] = float(t[3])
+                self.rules[rule_name + '_' + str(self.rulenumber)] = d
+                self.rulenumber = self.rulenumber + 1
+        if rule_type in ['function']:
+            d = {'type': rule_type,
+                 'movement': None}
+            if rule_type == 'function':
+                movement = [x.strip() for x in actions[0].split('->')]
+                d['movement'] = [(loc.split('.')[0], loc.split('.')[1]) 
+                                 for loc in movement]
+                d['function'] = actions[1]
+                d['conditions'] = [cond.strip() 
+                                   for cond in actions[2].split(';')]
+            self.rules[rule_name + '_' + str(self.rulenumber)] = d
+            self.rulenumber = self.rulenumber + 1
+        
+    def _step_rule(self, movement, value, interval):
+        '''!
+        Private method which simulates a step rule action.
+        
+        @param movement string: defines the movement of a token type. 
+        Each movement is defined in the following format: <source 
+        place>.<source token> -> <destination place>.<destination 
+        token>
+        @param value float: the number of tokens to move
+        @param interval integer: simulation time interval
+        '''
+        source_place = self.places[movement[0][0]]
+        source_value = movement[0][1]
+        destination_place = self.places[movement[1][0]]
+        destination_value = movement[1][1]
+        if source_place.attributes[source_value] < (value*interval) \
+            and self.zerolowerbound == True:
+            value = source_place.attributes[source_value]
+        source_place.attributes[source_value] = \
+            source_place.attributes[source_value] - (value*interval)
+        destination_place.attributes[destination_value] = \
+            destination_place.attributes[destination_value] + \
+            (value*interval)
+    
+    def _test_condition(self, place, token, operator, value):
+        '''!
+        Private method used by rule processors for logical check of 
+        condition. For example, the condition 'mixer.flour == 0' will 
+        be written as 
+        
+        >>> _test_condition('mixer', 'flour', '==', 0)
+        
+        @param place string: name of place/container
+        @param token string: name of token
+        @param operator string: binary operator. Allowable values are 
+        '==' (equals to), '>' (more than), '>=' (more than or equals 
+        to), '<' (less than), '<=' (less than or equals to), and '!=' (
+        not equals to).
+        @param value: value to be checked
+        @return 'passed' if test result is true, or 0 if test result 
+        is false
+        '''
+        value = float(value)
+        if operator == '==' and \
+            self.places[place].attributes[token] == value:
+            return 'passed'
+        elif operator == '>' and \
+            self.places[place].attributes[token] > value:
+            return 'passed'
+        elif operator == '>=' and \
+            self.places[place].attributes[token] >= value:
+            return 'passed'
+        elif operator == '<' and \
+            self.places[place].attributes[token] < value:
+            return 'passed'
+        elif operator == '<=' and \
+            self.places[place].attributes[token] <= value:
+            return 'passed'
+        elif operator == '!=' and \
+            self.places[place].attributes[token] != value:
+            return 'passed'
+        else:
+            return 'failed'
+    
+    def _conditions_processor(self, conditions):
+        '''!
+        Private method used by rule processors to evaluate logical 
+        conditions.
+        
+        @param conditions list: one or more logical conditions in the 
+        format of '<place>.<token> <binary operator> <criterion>', 
+        such as 'oven.heat > 300', for evaluation
+        '''
+        test = [0] * len(conditions)
+        for i in range(len(conditions)):
+            cond = conditions[i]
+            if len(cond.split('==')) == 2:
+                operator = '=='
+                cond = [c.strip() for c in cond.split('==')]
+            elif len(cond.split('>')) == 2:
+                operator = '>'
+                cond = [c.strip() for c in cond.split('>')]
+            elif len(cond.split('>=')) == 2:
+                operator = '>='
+                cond = [c.strip() for c in cond.split('>=')]
+            elif len(cond.split('<')) == 2:
+                operator = '<'
+                cond = [c.strip() for c in cond.split('<')]
+            elif len(cond.split('<=')) == 2:
+                operator = '<='
+                cond = [c.strip() for c in cond.split('<=')]
+            elif len(cond.split('!=')) == 2:
+                operator = '!='
+                cond = [c.strip() for c in cond.split('!=')]
+            source_place = cond[0].split('.')[0]
+            source_value = cond[0].split('.')[1]
+            criterion = cond[1]
+            test[i] = self._test_condition(source_place, source_value, 
+                                           operator, criterion)
+        return test
+        
+    def _incubate_rule(self, rule, interval):
+        '''!
+        Private method which simulates an incubate rule action.
+        
+        @param rule: a dictionary representing the incubate rule
+        @param interval integer: simulation time interval
+        @return modified rule dictionary
+        '''
+        value = rule['value']
+        timer = rule['timer']
+        conditions = rule['conditions']
+        movement = rule['movement']
+        test = self._conditions_processor(conditions)
+        if len(['failed' for t in test if t == 'failed']) == 0:
+            if (timer + interval) < value:
+                rule['timer'] = timer + interval
+            else:
+                source_place = self.places[movement[0][0]]
+                source_value = movement[0][1]
+                destination_place = self.places[movement[1][0]]
+                destination_value = movement[1][1]
+                destination_place.attributes[destination_value] = \
+                    destination_place.attributes[destination_value] + \
+                    source_place.attributes[source_value]
+                source_place.attributes[source_value] = 0
+                rule['timer'] = 0
+        return rule
+    
+    def _ratio_rule(self, movement, ratio, limit_check, 
+                    limit_set, interval):
+        '''!
+        Private method which simulates a ratio rule action.
+        
+        @param movement string: defines the movement of a token type. 
+        Each movement is defined in the following format: <source 
+        place>.<source token> -> <destination place>.<destination 
+        token>
+        @param ratio float: the ratio of tokens to move
+        @param limit_check string: logical check for remainder value
+        @param limit_set flaot: value to set token if token in 
+        limit_check is true
+        @param interval integer: simulation time interval
+        '''
+        source_place = self.places[movement[0][0]]
+        source_value = movement[0][1]
+        destination_place = self.places[movement[1][0]]
+        destination_value = movement[1][1]
+        # Step 1: Perform ratio rule operation
+        token_value = source_place.attributes[source_value] * \
+                      ratio * interval
+        source_place.attributes[source_value] = \
+            source_place.attributes[source_value] - token_value
+        destination_place.attributes[destination_value] = \
+            destination_place.attributes[destination_value] + token_value
+        # Step 2: Perform remaining checks and corrections
+        if len(limit_check.split('>')) == 2:
+            operator = '>'
+            limit_check = [c.strip() for c in limit_check.split('>')]
+        if len(limit_check.split('<')) == 2:
+            operator = '<'
+            limit_check = [c.strip() for c in limit_check.split('<')]
+        check_place = limit_check[0].split('.')[0]
+        check_token = limit_check[0].split('.')[1]
+        check_value = float(limit_check[1])
+        if self._test_condition(check_place, check_token, 
+            operator, check_value) == 'passed':
+            if source_value in self.losses:
+                self.losses[source_value] = self.losses[source_value] + \
+                source_place.attributes[source_value] - limit_set
+            else:
+                self.losses[source_value] = \
+                source_place.attributes[source_value] - limit_set
+            source_place.attributes[source_value] = limit_set
+    
+    def _function_rule(self, movement, function, conditions):
+        '''!
+        Private method which simulates a function rule action.
+        
+        @param movement string: defines the movement of a token type. 
+        Each movement is defined in the following format: <source 
+        place>.<source token> -> <destination place>.<destination 
+        token>
+        @param function function: a Python function to be executed 
+        when conditions are met. This function describes the 
+        transition of token.
+        @param conditions list: one or more logical conditions in the 
+        format of '<place>.<token> <binary operator> <criterion>', 
+        such as 'oven.heat > 300', for evaluation
+        '''
+        source_place = self.places[movement[0][0]]
+        source_value = movement[0][1]
+        destination_place = self.places[movement[1][0]]
+        destination_value = movement[1][1]
+        test = self._conditions_processor(conditions)
+        if len(['failed' for t in test if t == 'failed']) == 0:
+            token_value = function(self.places)
+            source_place.attributes[source_value] = \
+                source_place.attributes[source_value] - token_value
+            destination_place.attributes[destination_value] = \
+                destination_place.attributes[destination_value] + token_value
+        
+    def _execute_rules(self, clock, interval):
+        '''!
+        Private method used by PNet.simulate() and 
+        PNet.simulate_yield() to execute all the rules.
+        
+        @param clock cloat: wall time of the current simulation
+        @param interval integer: simulation time interval
+        '''
+        affected_places = []
+        for rName in self.rules.keys():
+            # Step rule
+            if self.rules[rName]['type'] == 'step':
+                movement = self.rules[rName]['movement']
+                value = self.rules[rName]['value']
+                self._step_rule(movement, value, interval)
+            # Delay rule
+            if self.rules[rName]['type'] == 'delay' and \
+                (clock % self.rules[rName]['delay']) == 0:
+                movement = self.rules[rName]['movement']
+                value = self.rules[rName]['value']
+                self._step_rule(movement, value, interval)
+            # Incubate rule
+            if self.rules[rName]['type'] == 'incubate':
+                value = self.rules[rName]['value']
+                rule = self._incubate_rule(self.rules[rName], interval)
+                self.rules[rName] = rule
+            # Ratio rule
+            if self.rules[rName]['type'] == 'ratio':
+                movement = self.rules[rName]['movement']
+                ratio = self.rules[rName]['ratio']
+                limit_check = self.rules[rName]['limit_check']
+                limit_set = self.rules[rName]['limit_set']
+                self._ratio_rule(movement, ratio, limit_check, 
+                                 limit_set, interval)
+            # Function rule
+            if self.rules[rName]['type'] == 'function':
+                movement = self.rules[rName]['movement']
+                conditions = self.rules[rName]['conditions']
+                function = self.rules[rName]['function']
+                self._function_rule(movement, function, conditions)
+       
+    def simulate(self, end_time, interval=1.0, report_frequency=1.0):
+        '''!
+        Method to simulate the Petri Net. This method stores the 
+        generated report in memory; hence, not suitable for extended 
+        simulations as it can run out of memory. It is possible to 
+        conserve memory by reducing the reporting frequency. Use 
+        simulate_yield method for extended simulations.
+        
+        @param end_time integer: number of time steps to simulate. If 
+        end_time = 1000, it can be 1000 seconds or 1000 days, 
+        depending on the significance of each step
+        @param interval float: number of intervals between each time 
+        step. Default = 1.0, simulate by time step interval
+        @param report_frequency float: number of time steps between 
+        each reporting. Default = 1.0, each time step is reported
+        '''
+        clock = 1
+        end_time = int(end_time)
+        while clock < (end_time + 1):
+            self._execute_rules(clock, interval)
+            if (clock % report_frequency) == 0: 
+                self._generate_report(clock)
+            clock = clock + interval
+
+    def simulate_yield(self, end_time, interval=1.0):
+        '''!
+        Method to simulate the Petri Net. This method runs as a 
+        generator, making it suitable for extended simulation.
+        
+        @param end_time integer: number of time steps to simulate. If 
+        end_time = 1000, it can be 1000 seconds or 1000 days, 
+        depending on the significance of each step
+        @param interval float: number of intervals between each time 
+        step. Default = 1.0, simulate by time step interval
+        '''
+        clock = 1
+        end_time = int(end_time)
+        while clock < end_time:
+            self._execute_rules(clock, interval)
+            self._generate_report(clock)
+            rept = {}
+            for k in self.report[str(clock)].keys():
+                rept[k] = self.report[str(clock)][k]
+            del self.report[str(clock)][k]
+            yield (clock, rept)
+            clock = clock + interval
+                
+    def _generate_report(self, clock):
+        '''!
+        Private method to generate and store report in memory of each 
+        token status (the value of each token) in every place/
+        container.
+        
+        @param clock float: step count of the current simulation
+        '''
+        rept = {}
+        for pName in self.places.keys():
+            for aName in self.places[pName].attributes.keys():
+                value = self.places[pName].attributes[aName]
+                name = '.'.join([pName, aName])
+                rept[name] = value
+        self.report[str(clock)] = rept
+        
+    def report_tokens(self, reportdict=None):
+        '''!
+        Method to report the status of each token(s) from each place 
+        as a list. This can be used in 2 different ways: to generate a 
+        list representation of a status from one time step (such as 
+        from simulate_yield method), or to generate a list 
+        representation of a status from entire simulation (such as 
+        from simulate method). 
+        
+        from simulate method
+        >>> net.simulate(65, 1, 1)
+        
+        >>> status = net.report_tokens()
+        
+        from simulate_yield method
+        >>> status = [d for d in net.simulate_yield(65, 1)]
+        
+        >>> status = [(d[0], net.report_tokens(d[1])) for d in status]
+        
+        @param reportdict dictionary: status from one time step. 
+        Default = None. If None, it will assume that simulate method 
+        had been executed and all status are stored in memory, and 
+        this method will generate a report from status stored in memory
+        @return tuple of ([<place.token name>], [([<place.token 
+        value>]]) if reportdict is given, or tuple of (time step, [<
+        place.token name>], [([<place.token value>]]) if reportdict is 
+        None.
+        '''
+        if reportdict:
+            placetokens = reportdict.keys()
+            tokenvalues = [reportdict[k] for k in placetokens]
+            return (placetokens, tokenvalues)
+        else:
+            timelist = list(self.report.keys())
+            datalist = [0] * len(timelist)
+            for i in range(len(timelist)):
+                placetokens = list(self.report[timelist[i]].keys())
+                tokenvalues = [self.report[timelist[i]][k] 
+                               for k in placetokens]
+                datalist[i] = (timelist[i], placetokens, tokenvalues)
+            return datalist
+        
\ No newline at end of file
diff --git a/copads/statisticsdistribution.py b/copads/statisticsdistribution.py
new file mode 100644
index 0000000..6369c08
--- /dev/null
+++ b/copads/statisticsdistribution.py
@@ -0,0 +1,3545 @@
+"""
+Classes for Various Statistical Distributions.
+
+References:
+    - Regress+ A compendium of common probability distributions (version 2.3)
+    by Michael P. McLaughlin (mpmcl@mitre.org)
+    http://www.causascientia.org/math_stat/Dists/Compendium.pdf
+    - Hand-book on statistical distributions for experimentalists
+    Internal report SUF-PFY/96-01. University of Stockholms
+    by Christian Walck (walck@physto.se)
+
+
+Distributions:
+    - BetaDistribution(location, scale, p, q)
+        - PowerFunctionDistribution(shape)
+    - BinomialDistribution(success, trial)
+        - BernoulliDistribution(success)
+    - BradfordDistribution
+    - BurrDistribution
+    - CauchyDistribution(location=0.0, scale=1.0)
+        - LorentzDistribution (alias of CauchyDistribution)
+    - ChiDistribution
+        - HalfNormalDistribution(location, scale)
+        - MaxwellDistribution(scale)
+        - RayleighDistribution(scale)
+    - CosineDistribution(location=0.0, scale=1.0)
+    - DoubleGammaDistribution
+    - DoubleWeibullDistribution
+    - ExponentialDistribution(location=0.0, scale=1.0)
+        - NegativeExponentialDistribution (alias of ExponentialDistribution)
+    - ExtremeLBDistribution
+    - FDistribution
+    - FiskDistribution
+        - LogLogisticDistribution (alias of FiskDistribution)
+    - FoldedNormalDistribution
+    - GammaDistribution
+        - ChiSquareDistribution(df)
+        - ErlangDistribution(shape)
+        - FurryDistribution (alias of GammaDistribution)
+    - GenLogisticDistribution
+    - GeometricDistribution(success=0.5)
+    - GumbelDistribution(location, scale)
+        - FisherTippettDistribution (alias of GumbelDistribution)
+        - GompertzDistribution  (alias of GumbelDistribution)
+        - LogWeibullDistribution (alias of GumbelDistribution)
+    - HyperbolicSecantDistribution
+    - HypergeometricDistribution
+    - InverseNormalDistribution
+        - WaldDistribution (alias of InverseNormalDistribution)
+    - LaplaceDistribution
+        - BilateralExponentialDistribution (alias of LaplaceDistribution)
+        - DoubleExponentialDistribution (alias of LaplaceDistribution)
+    - LogarithmicDistribution(shape)
+    - LogisticDistribution
+        - SechSquaredDistribution (alias of LogisticDistribution)
+    - LogNormalDistribution
+        - AntiLogNormalDistribution (alias of LogNormalDistribution)
+        - CobbDouglasDistribution (alias of LogNormalDistribution)
+    - NakagamiDistribution
+    - NegativeBinomialDistribution(success, target)
+        - PascalDistribution(success, target)
+        - PolyaDistribution (alias of NegativeBinomialDistribution)
+    - NormalDistribution()
+    - ParetoDistribution(location=1.0, shape=1.0)
+    - PoissonDistribution(expectation)
+    - RademacherDistribution()
+    - ReciprocalDistribution
+    - SemicircularDistribution(location=0.0, scale=1.0)
+    - TDistribution(location=0.0, scale=1.0, shape=2)
+    - TriangularDistribution
+    - UniformDistribution(location, scale)
+        - RectangularDistribution (alias of UniformDistribution)
+    - WeibullDistribution
+        - FrechetDistribution (alias of WeibullDistribution)
+
+
+Copyright (c) Maurice H.T. Ling <mauriceling@acm.org>
+
+Date created: 17th August 2005
+"""
+
+import math
+import random
+
+from copadsexceptions import DistributionParameterError
+from copadsexceptions import DistributionFunctionError
+from copadsexceptions import NormalDistributionTypeError
+from constants import *
+import nrpy
+
+class Distribution:
+    """
+    Abstract class for all statistical distributions.
+    Due to the large variations of parameters for each distribution, it is
+    unlikely to be able to standardize a parameter list for each method that
+    is meaningful for all distributions. Instead, the parameters to construct
+    each distribution is to be given as keyword arguments.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+    """
+
+    def __init__(self, **parameters):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+        """
+        raise NotImplementedError
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability. CDF is
+        also known as density function.
+        """
+        raise NotImplementedError
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution.
+        """
+        raise NotImplementedError
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis.
+        """
+        raise NotImplementedError
+
+    def mean(self):
+        """
+        Gives the arithmetic mean of the sample.
+        """
+        raise NotImplementedError
+
+    def mode(self):
+        """
+        Gives the mode of the sample, if closed-form is available.
+        """
+        raise NotImplementedError
+
+    def kurtosis(self):
+        """
+        Gives the kurtosis of the sample.
+        """
+        raise NotImplementedError
+
+    def skew(self):
+        """
+        Gives the skew of the sample.
+        """
+        raise NotImplementedError
+
+    def variance(self):
+        """
+        Gives the variance of the sample.
+        """
+        raise NotImplementedError
+
+# ----------------------------------------------------------
+# Tested Distributions
+# ----------------------------------------------------------
+
+class BetaDistribution(Distribution):
+    """
+    Class for Beta Distribution.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested method
+    @since: version 0.2
+    """
+
+    def __init__(self, location, scale, p, q):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location:
+        @param scale: upper bound
+        @param p: shape parameter. Although no upper bound but seldom exceed 10.
+        @param q: shape parameter. Although no upper bound but seldom exceed 10.
+        """
+        self.location = float(location)
+        self.scale = float(scale)
+        self.p = float(p)
+        self.q = float(q)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability.
+        """
+        return nrpy.betai(self.p, self.q, (x - self.location)/
+                         (self.scale - self.location))
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability
+        for particular value of x, or the area under probability
+        distribution from x-h to x+h for continuous distribution.
+        """
+        n = (self.scale - self.location) ** (self.p + self.q - 1)
+        n = nrpy.gammln(self.p) * nrpy.gammln(self.q) * n
+        n = nrpy.gammln(self.p + self.q) / n
+        p = (x - self.location) ** (self.p - 1)
+        q = (self.scale - x) ** (self.q - 1)
+        return n * p * q
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability
+        value and returns the corresponding value on the x-axis.
+        """
+        cprob = self.CDF(start)
+        if probability < cprob:
+            return (start, cprob)
+        while probability > cprob:
+            start = start + step
+            cprob = self.CDF(start)
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        n = (self.location * self.q) + (self.scale * self.p)
+        return n / (self.p + self.q)
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        n = (self.location * (self.q - 1)) + (self.scale * \
+            (self.p - 1))
+        return n / (self.p + self.q - 2)
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        n = (self.p ** 2) * (self.q + 2) + \
+            (2 * (self.q ** 2)) + \
+            ((self.p * self.q) * (self.q - 2))
+        n = n * (self.p + self.q + 1)
+        d = self.p * self.q * (self.p + self.q + 2) * \
+            (self.p + self.q + 3)
+        return 3 * ((n / d) - 1)
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        d = (self.p + self.q) ** 3
+        d = d * (self.p + self.q + 1) * (self.p + self.q + 2)
+        e = ((self.p + self.q) ** 2) * (self.p + self.q + 1)
+        e = (self.p * self.q) / e
+        e = e ** 1.5
+        return ((2 * self.p * self.q) * (self.q - self.q)) / (d * e)
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        n = self.p * self.q * ((self.scale - self.location) ** 2)
+        d = (self.p + self.q + 1) * ((self.p + self.q) ** 2)
+        return n / d
+
+    def moment(self, r):
+        """Gives the r-th moment of the sample."""
+        return nrpy.beta(self.p + r,
+            self.q)/nrpy.beta(self.p, self.q)
+
+    def random(self):
+        """Gives a random number based on the distribution."""
+        return random.betavariate(self.p, self.q)
+
+
+class BinomialDistribution(Distribution):
+    """
+    Class for Binomial Distribution.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested method
+    @since: version 0.2
+    """
+
+    def __init__(self, success=0.5, trial=1000):
+        """
+        Constructor method. The parameters are used to construct
+        the probability distribution.
+
+        @param success: probability of success; 0 <= success <= 1
+        @param trial: number of Bernoulli trials
+        """
+        self.success = float(success)
+        self.trial = int(trial)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability.
+        """
+        return nrpy.cdf_binomial(x, self.trial, self.success)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution.
+        """
+        x = int(x)
+        return nrpy.bico(self.trial, x) * \
+            (self.success ** x) * \
+            ((1 - self.success) ** (self.trial - x))
+
+    def inverseCDF(self, probability, start=0, step=1):
+        """
+        It does the reverse of CDF() method, it takes a probability
+        value and returns the corresponding value on the x-axis.
+        """
+        cprob = self.CDF(start)
+        if probability < cprob:
+            return (start, cprob)
+        while probability > cprob:
+            start = start + step
+            cprob = self.CDF(start)
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.success * self.trial
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return int(self.success * (self.trial + 1))
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return (1 - ((6 * self.success * (1 - self.success))) /
+            (self.trial * self.success * (1 - self.success)))
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return (1 - self.success - self.success)/ \
+            ((self.trial * self.success * (1 - self.success)) ** 0.5)
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.mean() * (1 - self.success)
+
+
+class CauchyDistribution(Distribution):
+    """
+    Class for Cauchy Distribution.
+
+    @see: Chen, KFQ, Ling, MHT. 2013. COPADS III (Compendium of
+    Distributions II): Cauchy, Cosine, Exponential, Hypergeometric,
+    Logarithmic, Semicircular, Triangular, and Weibull. The Python
+    Papers Source Codes 5: 2.
+
+    @status: Tested method
+    @since: version 0.4
+    """
+
+    def __init__(self, location=0.0, scale=1.0):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location: the mean; default = 0.0
+        @param scale: spread of the distribution, S{lambda}; default = 1.0
+        """
+        self.location = location
+        self.scale = scale
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return 0.5 + 1 / PI * math.atan((x - self.location) / self.scale)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return 1 / (PI * self.scale * \
+            (1 + (((x - self.location) / self.scale) ** 2)))
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        raise DistributionFunctionError('Mean for Cauchy Distribution is \
+            undefined')
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.location
+
+    def median(self):
+        """Gives the median of the sample."""
+        return self.location
+
+    def quantile1(self):
+        """Gives the 1st quantile of the sample."""
+        return self.location - self.scale
+
+    def quantile3(self):
+        """Gives the 3rd quantile of the sample."""
+        return self.location + self.scale
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return 0.5
+
+    def random(self, seed):
+        """Gives a random number based on the distribution."""
+        while 1:
+            seed = self.loaction + (self.scale * math.tan(PI * (seed - 0.5)))
+            yield seed
+
+
+class CosineDistribution(Distribution):
+    """
+    Cosine distribution is sometimes used as a simple approximation to
+    Normal distribution.
+
+    @see: Chen, KFQ, Ling, MHT. 2013. COPADS III (Compendium of
+    Distributions II): Cauchy, Cosine, Exponential, Hypergeometric,
+    Logarithmic, Semicircular, Triangular, and Weibull. The Python
+    Papers Source Codes 5: 2.
+
+    @status: Tested method
+    @since: version 0.4
+    """
+
+    def __init__(self, location=0.0, scale=1.0):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location: the mean; default = 0.0
+        @param scale: spread of the distribution, S{lambda}; default = 1.0
+        """
+        self.location = location
+        self.scale = scale
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        n = PI + (x - self.location) / self.scale + \
+            math.sin((x - self.location) / self.scale)
+        return n / PI2
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return (1 / (PI2 * self.scale)) * \
+                (1 + math.cos((x - self.location) / self.scale))
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.location
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.location
+
+    def median(self):
+        """Gives the median of the sample."""
+        return self.location
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return -0.5938
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 0.0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return (((PI * PI)/3) - 2) * (self.scale ** 2)
+
+    def quantile1(self):
+        """Gives the 1st quantile of the sample."""
+        return self.location - (0.8317 * self.scale)
+
+    def quantile3(self):
+        """Gives the 13rd quantile of the sample."""
+        return self.location + (0.8317 * self.scale)
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        return 0.5
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return 0.5
+
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class ExponentialDistribution(Distribution):
+    """
+    Exponential distribution is the continuous version of Geometric
+    distribution. It is also a special case of Gamma distribution where
+    shape = 1
+
+    @see: Chen, KFQ, Ling, MHT. 2013. COPADS III (Compendium of
+    Distributions II): Cauchy, Cosine, Exponential, Hypergeometric,
+    Logarithmic, Semicircular, Triangular, and Weibull. The Python
+    Papers Source Codes 5: 2.
+
+    @status: Tested method
+    @since: version 0.4
+    """
+
+    def __init__(self, location=0.0, scale=1.0):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location: position of the distribution, default = 0.0
+        @param scale: spread of the distribution, S{lambda}; default = 1.0"""
+        self.location = location
+        self.scale = scale
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return 1 - math.exp((self.location - x) / self.scale)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return (1/self.scale) * math.exp((self.location - x)/self.scale)
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.location + self.scale
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.location
+
+    def median(self):
+        """Gives the median of the sample."""
+        return self.location + (self.scale * math.log10(2))
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return 6.0
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 2.0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.scale * self.scale
+
+    def quantile1(self):
+        """Gives the 1st quantile of the sample."""
+        return self.location + (self.scale * math.log10(1.333))
+
+    def quantile3(self):
+        """Gives the 3rd quantile of the sample."""
+        return self.location + (self.scale * math.log10(4))
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        return 0.6321
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return 0.0
+
+    def random(self):
+        """Gives a random number based on the distribution."""
+        return random.expovariate(1/self.location)
+
+
+class FDistribution(Distribution):
+    """
+    Class for F Distribution.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested method
+    @since: version 0.2
+    """
+
+    def __init__(self, df1=1, df2=1):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param df1: degrees of freedom for numerator
+        @param df2: degrees of freedom for denorminator
+        """
+        self.df1 = float(df1)
+        self.df2 = float(df2)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability.
+        """
+        sub_x = (self.df1 * x) / (self.df1 * x + self.df2)
+        return nrpy.betai(self.df1 / 2.0, self.df2 / 2.0, sub_x)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability
+        for particular value of x, or the area under probability
+        distribution from x-h to x+h for continuous distribution.
+        """
+        x = float(x)
+        n1 = ((x * self.df1) ** self.df1) * (self.df2 ** self.df2)
+        n2 = (x * self.df1 + self.df2) ** (self.df1 + self.df2)
+        d = x * nrpy.beta(self.df1 / 2.0, self.df2 / 2.0)
+        return math.sqrt(n1 / n2) / d
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        the corresponding value on the x-axis.
+        """
+        cprob = self.CDF(start)
+        if probability < cprob:
+            return (start, cprob)
+        while probability > cprob:
+            start = start + step
+            cprob = self.CDF(start)
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return float(self.df2 / (self.df2 - 2))
+
+
+class GammaDistribution(Distribution):
+    """
+    Class for Gamma Distribution.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested method
+    @since: version 0.2
+    """
+
+    def __init__(self, location, scale, shape):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location:
+        @param scale:
+        @param shape:"""
+        self.location = float(location)
+        self.scale = float(scale)
+        self.shape = float(shape)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability.
+        """
+        return nrpy.gammp(self.shape, (x - self.location) / self.scale)
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        the corresponding value on the x-axis.
+        """
+        cprob = self.CDF(start)
+        if probability < cprob:
+            return (start, cprob)
+        while probability > cprob:
+            start = start + step
+            cprob = self.CDF(start)
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.location + (self.scale * self.shape)
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.location + (self.scale * (self.shape - 1))
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return 6 / self.shape
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 2 / math.sqrt(self.shape)
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.scale * self.scale * self.shape
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        return nrpy.gammp(self.shape, self.shape)
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return nrpy.gammp(self.shape, self.shape - 1)
+
+
+def ErlangDistribution(location, scale, shape):
+    """
+    Erlang distribution is an alias of Gamma distribution where the shape
+    parameter is an integer.
+
+    @param location:
+    @param scale:
+    @param shape:
+
+    @status: Tested method
+    @since: version 0.2
+    """
+    return GammaDistribution(location, scale, int(shape))
+
+
+def FurryDistribution(location, scale, shape):
+    """
+    Furry distribution is an alias of Gamma distribution.
+
+    @param location:
+    @param scale:
+    @param shape:
+
+    @status: Tested method
+    @since: version 0.2
+    """
+    return GammaDistribution(location, scale, shape)
+
+
+class ChiSquareDistribution(GammaDistribution):
+    """
+    Chi-square distribution is a special case of Gamma distribution where
+    location = 0, scale = 2 and shape is twice that of the degrees of freedom.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested method
+    @since: version 0.2
+    """
+
+    def __init__(self, df=2):
+        """
+        Constructor method. The parameters are used to construct
+        the probability distribution.
+
+        @param df: degrees of freedom"""
+        GammaDistribution.__init__(self, 0, 2, float(df) / 2.0)
+
+
+class GeometricDistribution(Distribution):
+    """
+    Geometric distribution is the discrete version of Exponential
+    distribution.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested method
+    @since: version 0.2
+    """
+
+    def __init__(self, success=0.5):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param success: probability of success; 0 <= success <= 1;
+            default = 0.5
+        """
+        self.prob = float(success)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability.
+        """
+        total = self.PDF(1)
+        for i in range(2, int(x) + 1):
+            total += self.PDF(i)
+        return total
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability
+        for particular value of x, or the area under probability
+        distribution from x-h to x+h for continuous distribution.
+        """
+        return self.prob * ((1 - self.prob) ** (x - 1))
+
+    def inverseCDF(self, probability, start=1, step=1):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        the corresponding value on the x-axis.
+        """
+        cprob = self.CDF(start)
+        if probability < cprob:
+            return (start, cprob)
+        while probability > cprob:
+            start = start + step
+            cprob = self.CDF(start)
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return 1/self.prob
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return 1.0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return (1 - self.prob) / (self.prob ** 2)
+
+
+class HypergeometricDistribution(Distribution):
+    """
+    Class for Hypergeometric distribution
+
+    @see: Chen, KFQ, Ling, MHT. 2013. COPADS III (Compendium of
+    Distributions II): Cauchy, Cosine, Exponential, Hypergeometric,
+    Logarithmic, Semicircular, Triangular, and Weibull. The Python
+    Papers Source Codes 5: 2.
+
+    @status: Tested method
+    @since: version 0.4
+    """
+
+    def __init__(self, sample_size,
+                 population_size=100,
+                 population_success=50):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param sample_size: sample size (not more than population size)
+        @type sample_size: integer
+        @param population_size: population size; default = 100
+        @type population_size: integer
+        @param population_success: number of successes in the population
+        (cannot be more than population size); default = 10
+        @type population_success: integer"""
+        if population_success > population_size:
+            raise AttributeError('population_success cannot be more \
+            than population_size')
+        elif sample_size > population_size:
+            raise AttributeError('sample_size cannot be more \
+            than population_size')
+        else:
+            self.psize = int(population_size)
+            self.psuccess = int(population_success)
+            self.ssize = int(sample_size)
+
+    def CDF(self, sample_success):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value (sample_success, an integer that is not more than sample
+        size) on the x-axis where y-axis is the probability."""
+        if sample_success > self.ssize:
+            raise AttributeError('sample_success cannot be more \
+            than sample_size')
+        else:
+            return sum([self.PDF(n) for n in range(1, sample_success+1)])
+
+    def PDF(self, sample_success):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x (sample_success, an integer that is not more
+        than sample size), or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        if sample_success > self.ssize:
+            raise AttributeError('sample_success cannot be more \
+            than sample_size')
+        else:
+            sample_success = int(sample_success)
+            numerator = nrpy.bico(self.psuccess, sample_success)
+            numerator = numerator  * nrpy.bico(self.psize-self.psuccess,
+                                               self.ssize-sample_success)
+            denominator = nrpy.bico(self.psize, self.ssize)
+            return float(numerator)/float(denominator)
+
+    def inverseCDF(self, probability, start=1, step=1):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (int(start), cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.ssize * (float(self.psuccess)/float(self.psize))
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        temp = (self.ssize + 1) * (self.psuccess + 1)
+        return float(temp)/float(self.psize + 2)
+
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        t1 = float(self.psize-self.psuccess)/float(self.psize)
+        t2 = float(self.psize-self.ssize)/float(self.psize-1)
+        return self.mean() * t1 * t2
+
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+##    def random(self, seed):
+##        """Gives a random number based on the distribution."""
+##        while 1:
+##            func
+##            yield seed
+
+
+class LogarithmicDistribution(Distribution):
+    """
+    Class for Logarithmic Distribution.
+
+    @see: Chen, KFQ, Ling, MHT. 2013. COPADS III (Compendium of
+    Distributions II): Cauchy, Cosine, Exponential, Hypergeometric,
+    Logarithmic, Semicircular, Triangular, and Weibull. The Python
+    Papers Source Codes 5: 2.
+
+    @status: Tested method
+    @since: version 0.4
+    """
+
+    def __init__(self, shape):
+        """Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param shape: the spread of the distribution"""
+        self.shape = shape
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        summation = 0.0
+        for i in range(int(x)): summation = summation + self.PDF(i)
+        return summation
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return (-1 * (self.shape ** x)) / (math.log10(1 - self.shape) * x)
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return (-1 * self.shape) / ((1 - self.shape) * \
+                math.log10(1 - self.shape))
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return 1.0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        n = (-1 * self.shape) * (self.shape + math.log10(1 - self.shape))
+        d = ((1 - self.shape) ** 2) * math.log10(1 - self.shape) * \
+            math.log10(1 - self.shape)
+        return n / d
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class NormalDistribution(Distribution):
+    """
+    Class for standardized normal distribution (area under the curve = 1)
+
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100
+    Statistical Tests. The Python Papers Source Codes 1:5
+
+    @status: Tested method
+    @since: version 0.1
+    """
+    def __init__(self):
+        self.mean = 0.0
+        self.stdev = 1.0
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability.
+        """
+        return 1.0 - 0.5 * nrpy.erfcc(x/SQRT2)
+
+    def PDF(self, x):
+        """
+        Calculates the density (probability) at x by the formula
+        f(x) = 1/(sqrt(2 pi) sigma) e^-((x^2/(2 sigma^2))
+        where mu is the mean of the distribution and sigma the standard
+        deviation.
+
+        @param x: probability at x
+        """
+        return (1/(math.sqrt(PI2) * self.stdev)) * \
+            math.exp(-(x ** 2/(2 * self.stdev**2)))
+
+    def inverseCDF(self, probability, start = -10.0,
+                   end = 10.0, error = 10e-8):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis, together with the
+        cumulative probability.
+
+        @param probability: probability under the curve from -infinity
+        @param start: lower boundary of calculation (default = -10)
+        @param end: upper boundary of calculation (default = 10)
+        @param error: error between the given and calculated probabilities
+        (default = 10e-8)
+        @return: Returns a tuple (start, cprob) where 'start' is the standard
+        deviation for the area under the curve from -infinity to the given
+        'probability' (+/- step). 'cprob' is the calculated area under the
+        curve from -infinity to the returned 'start'.
+        """
+        # check for tolerance
+        if abs(self.CDF(start)-probability) < error:
+            return (start, self.CDF(start))
+        # case 1: lower than -10 standard deviations
+        if probability < self.CDF(start):
+            return self.inverseCDF(probability, start-5, start, error)
+        # case 2: between -10 to 10 standard deviations (bisection method)
+        if probability > self.CDF(start) and \
+        probability < self.CDF((start+end)/2):
+            return self.inverseCDF(probability, start, (start+end)/2, error)
+        if probability > self.CDF((start+end)/2) and \
+        probability < self.CDF(end):
+            return self.inverseCDF(probability, (start+end)/2, end, error)
+        # case 3: higher than 10 standard deviations
+        if probability > self.CDF(end):
+            return self.inverseCDF(probability, end, end+5, error)
+
+    def mean(self):
+        return self.mean
+
+    def mode(self):
+        return self.mean
+
+    def kurtosis(self):
+        return 0.0
+
+    def skew(self):
+        return 0.0
+
+    def variance(self):
+        return self.stdev * self.stdev
+
+    def random(self):
+        """Gives a random number based on the distribution."""
+        return random.gauss(self.mean, self.stdev)
+
+
+class PoissonDistribution(Distribution):
+    """
+    Class for Poisson Distribution. Poisson distribution is binomial
+    distribution with very low success - that is, for rare events.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested method
+    @since: version 0.2
+    """
+
+    def __init__(self, expectation=0.001):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param expectation: mean success probability; S{lambda}
+        """
+        self._mean = float(expectation)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability.
+        """
+        return nrpy.cdf_poisson(x + 1, self._mean)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability
+        for particular value of x, or the area under probability
+        distribution from x-h to x+h for continuous distribution.
+        """
+        return (math.exp(-1 ** self._mean) *
+                (self._mean ** x)) / nrpy.factrl(x)
+
+    def inverseCDF(self, probability, start=0.001, step=1):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        the corresponding value on the x-axis.
+        """
+        cprob = self.CDF(start)
+        if probability < cprob:
+            return (start, cprob)
+        while probability > cprob:
+            start = start + step
+            cprob = self.CDF(start)
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self._mean
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return int(self._mean)
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self._mean
+
+
+class SemicircularDistribution(Distribution):
+    """
+    Class for Semicircular Distribution.
+
+    @see: Chen, KFQ, Ling, MHT. 2013. COPADS III (Compendium of
+    Distributions II): Cauchy, Cosine, Exponential, Hypergeometric,
+    Logarithmic, Semicircular, Triangular, and Weibull. The Python
+    Papers Source Codes 5: 2.
+
+    @status: Tested method
+    @since: version 0.4
+    """
+
+    def __init__(self, location=0.0, scale=1.0):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location: mean of the distribution, default = 0.0
+        @param scale: spread of the distribution, default = 1.0"""
+        self.location = location
+        self.scale = scale
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        t = (x - self.location) / self.scale
+        return 0.5 + (1 / PI) * (t * math.sqrt(1 - (t ** 2)) + math.asin(t))
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return (2 / (self.scale * PI)) * \
+                math.sqrt(1 - ((x - self.location) / self.scale) ** 2)
+
+    def inverseCDF(self, probability, start=-10.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        if start < -1 * self.scale:
+            start = -1 * self.scale
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.location
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.location
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return -1.0
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 0.0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return 0.25 * (self.scale ** 2)
+
+    def quantile1(self):
+        """Gives the 1st quantile of the sample."""
+        return self.location - (0.404 * self.scale)
+
+    def quantile3(self):
+        """Gives the 3rd quantile of the sample."""
+        return self.location + (0.404 * self.scale)
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        return 0.5
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return 0.5
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class TDistribution(Distribution):
+    """
+    Class for Student's t-distribution.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested method
+    @since: version 0.2
+    """
+
+    def __init__(self, location=0.0, scale=1.0, shape=2):
+        """Constructor method. The parameters are used to construct
+        the probability distribution.
+
+        @param location: default = 0.0
+        @param scale: default = 1.0
+        @param shape: degrees of freedom; default = 2"""
+        self._mean = float(location)
+        self.stdev = float(scale)
+        self.df = float(shape)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability.
+        """
+        t = (x - self._mean) / self.stdev
+        a = nrpy.betai(self.df / 2.0, 0.5, self.df / (self.df + (t * t)))
+        if t > 0:
+            return 1 - 0.5 * a
+        else:
+            return 0.5 * a
+
+    def PDF(self, x):
+        """
+        Calculates the density (probability) at x with n-th degrees of freedom
+        as
+        M{f(x) = S{Gamma}((n+1)/2) /
+        (sqrt(n * pi) S{Gamma}(n/2)) (1 + x^2/n)^-((n+1)/2)}
+
+        for all real x. It has mean 0 (for n > 1) and variance n/(n-2)
+        (for n > 2)."""
+        a = nrpy.gammln((self.df + 1) / 2)
+        b = math.sqrt(math.pi * self.df) * nrpy.gammln(self.df / 2) * \
+            self.stdev
+        c = 1 + ((((x - self._mean) / self.stdev) ** 2) / self.df)
+        return (a / b) * (c ** ((-1 - self.df) / 2))
+
+    def inverseCDF(self, probability, start = -10.0,
+                   end = 10.0, error = 10e-8):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis, together with the
+        cumulative probability.
+
+        @param probability: probability under the curve from -infinity
+        @param start: lower boundary of calculation (default = -10)
+        @param end: upper boundary of calculation (default = 10)
+        @param error: error between the given and calculated probabilities
+        (default = 10e-8)
+        @return: Returns a tuple (start, cprob) where 'start' is the standard
+        deviation for the area under the curve from -infinity to the given
+        'probability' (+/- step). 'cprob' is the calculated area under the
+        curve from -infinity to the returned 'start'.
+        """
+        # check for tolerance
+        if abs(self.CDF(start)-probability) < error:
+            return (start, self.CDF(start))
+        # case 1: lower than -10 standard deviations
+        if probability < self.CDF(start):
+            return self.inverseCDF(probability, start-10, start, error)
+        # case 2: between -10 to 10 standard deviations (bisection method)
+        if probability > self.CDF(start) and \
+        probability < self.CDF((start+end)/2):
+            return self.inverseCDF(probability, start, (start+end)/2, error)
+        if probability > self.CDF((start+end)/2) and \
+        probability < self.CDF(end):
+            return self.inverseCDF(probability, (start+end)/2, end, error)
+        # case 3: higher than 10 standard deviations
+        if probability > self.CDF(end):
+            return self.inverseCDF(probability, end, end+10, error)
+        # cprob = self.CDF(start)
+        # if probability < cprob:
+            # return (start, cprob)
+        # while probability > cprob:
+            # start = start + step
+            # cprob = self.CDF(start)
+        # return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self._mean
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self._mean
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        a = ((self.df - 2) ** 2) * nrpy.gammln((self.df / 2) - 2)
+        return 3 * ((a / (4 * nrpy.gammln(self.df / 2))) - 1)
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 0.0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return (self.df / (self.df - 2)) * self.stdev * self.stdev
+
+
+class TriangularDistribution(Distribution):
+    """
+    Class for Triangular Distribution.
+
+    @see: Chen, KFQ, Ling, MHT. 2013. COPADS III (Compendium of
+    Distributions II): Cauchy, Cosine, Exponential, Hypergeometric,
+    Logarithmic, Semicircular, Triangular, and Weibull. The Python
+    Papers Source Codes 5: 2.
+
+    @status: Tested method
+    @since: version 0.4
+    """
+    def __init__(self, upper_limit, peak, lower_limit=0):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param upper_limit: upper limit of the distrbution
+        @type upper_limit: float
+        @param peak: peak of the distrbution, which has to be between
+        the lower and upper limits of the distribution
+        @type peak: float
+        @param lower_limit: lower limit of the distrbution,
+        default = 0
+        @type lower_limit: float"""
+        self.lower_limit = lower_limit
+        if upper_limit < self.lower_limit:
+            raise AttributeError
+        else:
+            self.upper_limit = upper_limit
+        if peak > upper_limit:
+            raise AttributeError
+        if peak < lower_limit + 0.001:
+            raise AttributeError
+        else:
+            self.mode = peak
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        if x < self.lower_limit:
+            raise AttributeError
+        if x > self.mode:
+            raise AttributeError
+        else:
+            return (( x - self.lower_limit) ** 2) / \
+                ((self.upper_limit - self.lower_limit) * \
+                 (self.mode - self.lower_limit))
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution
+        from x-h to x+h for continuous distribution."""
+        if x < self.lower_limit:
+            raise AttributeError
+        if x > self.mode:
+            raise AttributeError
+        else:
+            return ((2 * (x - self.lower_limit)) / \
+                    ((self.upper_limit - self.lower_limit) * \
+                     (self.mode - self.lower_limit)))
+
+    def inverseCDF(self, probability, start=0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        start = self.lower_limit
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return (float(self.lower_limit + self.upper_limit + self.mode) / 3)
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return (self.mode)
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return ((-3)*(5 ** - 1))
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return (math.sqrt(2) * (self.lower_limit + self.upper_limit - 2 * \
+        self.mode) * (2 * self.lower_limit - self.self.upper_limit - self.mode) \
+         * (self.lower_limit - 2 * self.upper_limit + self.mode)) \
+         / (self.lower_limit ** 2 + self.upper_limit ** 2 + self.mode ** 2 - \
+        self.lower_limit * self.upper_limit + self.mode ** 2 - self.lower_limit * \
+        (self.upper_limit - self.mode))
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return (self.lower_limit ** 2 + self.upper_limit ** 2 + self.mode ** 2\
+        - (self.lower_limit * self.upper_limit) - \
+        (self.lower_limit * self.mode) - (self.upper_limit * self.mode))\
+        *(18 ** -1)
+
+    def quantile1(self):
+        """Gives the 1st quantile of the sample."""
+        if ((self.mode - self.lower_limit) * \
+        (self.upper_limit - self.lower_limit) ** -1) > 0.25:
+            return self.lower_limit + (0.5 * math.sqrt((self.upper_limit - \
+            self.lower_limit) * (self.mode - self.lower_limit)))
+        else:
+            return self.upper_limit - ((0.5) * math.sqrt (3 * (self.upper_limit -\
+            self.lower_limit) * (self.upper_limit - self.mode)))
+
+    def quantile3(self):
+        """Gives the 3rd quantile of the sample."""
+        if ((self.mode - self.lower_limit) * \
+        (self.upper_limit - self.lower_limit) ** -1) > 0.75:
+            return self.lower_limit + (0.5 * math.sqrt(3 * (self.upper_limit - \
+            self.lower_limit) * (self.mode - self.lower_limit)))
+        else:
+            return self.upper_limit - ((0.5) * math.sqrt ((self.upper_limit -\
+            self.lower_limit) * (self.upper_limit - self.mode)))
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        if self.mode > ((self.lower_limit + self.upper_limit) * 0.5):
+            return ((self.upper_limit + self.mode - 2 * self.lower_limit) ** 2)\
+            * (9 * (self.upper_limit - self.lower_limit) * (self.mode - \
+            self.lower_limit))
+        else:
+            return (self.lower_limit ** 2 + (5 * self.lower_limit * \
+            self.upper_limit) - (5 * (self.upper_limit ** 2)) - \
+            (7 * self.lower_limit * self.mode) + (5 * self. upper_limit * \
+            self.mode) + self.mode ** 2)
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return (self.mode - self.lower_limit) * (self.upper_limit \
+        - self.lower_limit) ** - 1
+
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class UniformDistribution(Distribution):
+    """
+    Class for Uniform distribution.
+
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python
+    Papers Source Codes 1:4
+
+    @status: Tested method
+    @since: version 0.2
+    """
+
+    def __init__(self, location, scale):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location:
+        @param scale:
+        """
+        self.location = float(location)
+        self.scale = float(scale)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability.
+        """
+        return (x - self.location) / (self.scale - self.location)
+
+    def PDF(self):
+        """
+        Partial Distribution Function, which gives the probability
+        for particular value of x, or the area under probability
+        distribution from x-h to x+h for continuous distribution.
+        """
+        return 1.0 / (self.scale - self.location)
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        the corresponding value on the x-axis.
+        """
+        cprob = self.CDF(start)
+        if probability < cprob:
+            return (start, cprob)
+        while probability > cprob:
+            start = start + step
+            cprob = self.CDF(start)
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return (self.location + self.scale) / 2.0
+
+    def median(self):
+        """Gives the median of the sample."""
+        return (self.location + self.scale) / 2
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return -1.2
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 0.0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return ((self.scale - self.location) ** 2) / 12
+
+    def quantile1(self):
+        """Gives the 1st quantile of the sample."""
+        return ((3 * self.location) + self.scale) / 4
+
+    def quantile3(self):
+        """Gives the 3rd quantile of the sample."""
+        return (self.location + (3 * self.scale)) / 4
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        return 0.5
+
+    def random(self, lower, upper):
+        """Gives a random number based on the distribution."""
+        return random.uniform(lower, upper)
+
+
+class WeiBullDistribution(Distribution):
+    """
+    Class for Weibull distribution.
+
+    @see: Chen, KFQ, Ling, MHT. 2013. COPADS III (Compendium of
+    Distributions II): Cauchy, Cosine, Exponential, Hypergeometric,
+    Logarithmic, Semicircular, Triangular, and Weibull. The Python
+    Papers Source Codes 5: 2.
+
+    @status: Tested method
+    @since: version 0.4
+    """
+    def __init__(self, location=1.0, scale=1.0):
+        """Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location: position of the distribution, default = 1.0
+        @param scale: shape of the distribution, default = 1.0"""
+        self.location = location
+        self.scale = scale
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the  probability curve) from -infinity or 0
+        to a give x-value on the x-axis where y-axis is the probability."""
+        power = -1 * ((float(x) / self.location) ** self.scale)
+        return 1 - (math.e ** power)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under trobability distribution
+        from x-h to x+h for continuous distribution."""
+        if x < 0:
+            return 0
+        else:
+            power = -1 * ((float(x) / self.location) ** self.scale)
+            t3 = math.e ** power
+            t2 = (float(x) / self.location) ** (self.scale - 1)
+            t1 = self.scale / self.location
+            return t1 * t2 * t3
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    # def mean(self):
+        # """Gives the arithmetic mean of the sample."""
+        # return self.location * nrpy.gammln(1 + 1/self.scale)
+
+    def median(self):
+        """Gives the median of the sample."""
+        return self.location * (math.log(2, math.e) ** (1/float(self.scale)))
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        if self.scale > 1:
+            t = ((self.scale - 1) / float(self.scale))
+            return self.location * (t ** (1/float(self.scale)))
+        else:
+            return 0
+
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+    def random(self):
+        """Gives a random number based on the distribution."""
+        return random.weibullvariate(self.scale, self.shape)
+
+
+def FrechetDistribution(**parameters):
+    """
+    Frechet distribution is an alias of Weibull distribution."""
+    return WeibullDistribution(**parameters)
+
+
+# ----------------------------------------------------------
+# Untested Distributions
+# ----------------------------------------------------------
+
+def AntiLogNormalDistribution(**parameters):
+    """
+    Anti-Lognormal distribution is an alias of Lognormal distribution."""
+    return LogNormalDistribution(**parameters)
+
+
+class BernoulliDistribution(Distribution):
+    """
+    Bernoulli distribution is a special case of Binomial distribution where
+    where number of trials = 1
+    """
+
+    def __init__(self, success):
+        """Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param success: probability of success; 0 <= success <= 1"""
+        self.distribution = BinomialDistribution(success, trial = 1)
+
+    def CDF(self, x):
+        """Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return self.distribution.CDF(x)
+
+    def PDF(self, x):
+        """Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return self.distribution.PDF(x)
+
+    def inverseCDF(self, probability, start = 0, step = 1):
+        """It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        return self.distribution.inverseCDF(probability, start, step)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.distribution.mean()
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.distribution.mode()
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return self.distribution.kurtosis()
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return self.distribution.skew()
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.distribution.variance()
+
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        return self.distribution.random()
+
+
+def BilateralExponentialDistribution(**parameters):
+    """
+    Bilateral Exponential distribution is an alias of Laplace distribution."""
+    return LaplaceDistribution(**parameters)
+
+
+class BradfordDistribution(Distribution):
+    """Class for Bradford distribution."""
+
+    def __init__(self, location, scale, shape):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location:
+        @param scale: upper bound
+        @param shape:"""
+        self.location = location
+        self.scale = scale
+        self.shape = shape
+        self.k = math.log10(self.shape + 1)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        r = ((self.shape * (x - self.location)) / (self.scale - self.location))
+        return math.log10(1 + r) / self.k
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution
+        from x-h to x+h for continuous distribution."""
+        r = (self.shape * (x - self.location)) + self.scale - self.location
+        return self.shape / (self.k * r)
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        r = self.shape * (self.scale - self.location)
+        r = r + (((self.shape + 1) * self.location - self.scale) * self.k)
+        return r / (self.shape * self.k)
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.location
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        d = ((self.shape * (self.k - 2)) + (2 * self.k)) ** 2
+        d = 3 * self.shape * d
+        n = ((self.k * ((3 * self.k) - 16)) + 24)
+        n = (self.shape ** 3) * (self.k - 3) * n
+        n = n + ((self.k - 4) * (self.k - 3) * (12 * self.k * (self.k **2)))
+        n = n + (6 * self.k * (self.k **2)) * ((3 * self.k) - 14)
+        return (n + (12 * (self.k ** 3))) / d
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        r = 12 * (self.shape ** 2)
+        r = r - (9 * self.k * self.shape * (self.shape + 2))
+        r = r + ((2 * self.k * self.k) * ((self.shape * (self.shape + 3)) + 3))
+        d = self.shape * (((self.k - 2) * self.shape) + (2 * self.k))
+        d = math.sqrt(d)
+        d = d * ((3 * self.shape * (self.k - 2)) + (6 * self.k))
+        return r / d
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        r = (self.scale - self.location) ** 2
+        r = r * (self.shape * (self.k - 2) +  (2 * self.k))
+        return r / (2 * self.shape * self.k * self.k)
+
+    def quantile1(self):
+        """Gives the 1st quantile of the sample."""
+        r = (self.location * (self.shape + 1)) - self.scale
+        r = r + ((self.scale - self.location) * ((self.shape + 1)** 0.25))
+        return r / self.shape
+
+    def quantile3(self):
+        """Gives the 3rd quantile of the sample."""
+        r = (self.location * (self.shape + 1)) - self.scale
+        r = r + ((self.scale - self.location) * ((self.shape + 1)** 0.75))
+        return r / self.shape
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        r = math.log10(self.shape / math.log10(self.shape + 1))
+        return r / math.log10(self.shape + 1)
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return 0.0
+
+    def random(self, seed):
+        """Gives a random number based on the distribution."""
+        while 1:
+            r = self.location * (self.shape + 1) - self.scale
+            r = r + ((self.scale - self.location)*((self.shape + 1) ** seed))
+            seed = r / self.shape
+            yield seed
+
+
+class BurrDistribution(Distribution):
+    """
+    Burr distribution is the generalization of Fisk distribution. Burr
+    distribution with D = 1 becomes Fisk distribution.
+    """
+    def __init__(self, location, scale, C, D):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location:
+        @param scale:
+        @param C: shape
+        @param D: shape"""
+        self.location = location
+        self.scale = scale
+        self.C = C
+        self.D = D
+        self.k = (nrpy.gammln(self.D) * \
+                    nrpy.gammln(1 - (2/self.C)) * \
+                    nrpy.gammln((2/self.C) + self.D)) - \
+                ((nrpy.gammln(1 - (1/self.C)) ** 2) * \
+                    (nrpy.gammln((1/self.C) + self.D) ** 2))
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return (1+(((x - self.location)/self.scale)**(-self.C)))**(-self.D)
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution
+        from x-h to x+h for continuous distribution."""
+        r = (1+(((x - self.location)/self.scale)**(-self.C)))**(-self.D - 1)
+        r = r * ((self.C * self.D)/self.scale)
+        return r * (((x - self.location)/self.scale)**(-self.C - 1))
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        r = nrpy.gammln(1 - (1/self.C)) * nrpy.gammln((1/self.C) + self.D)
+        return self.location + ((r * self.scale) / nrpy.gammln(self.D))
+    def mode(self):
+        """Gives the mode of the sample."""
+        if ((self.C * self.D) < 1): return self.location
+        else:
+            r = (((self.C * self.D)-1)/(self.C + 1)) ** (1/self.C)
+            return self.location + (self.scale * r)
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+    def variance(self):
+        """Gives the variance of the sample."""
+        return (self.k * (self.scale ** 2)) / (nrpy.gammln(self.D) ** 2)
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        if ((self.C * self.D) < 1): return 0.0
+        else:
+            return (1 + ((self.C+1)/((self.C*self.D) - 1))) ** (-1*self.D)
+    def random(self, seed):
+        """Gives a random number based on the distribution."""
+        while 1:
+            r = ((1/(seed ** (1/self.D))) - 1) ** (-1/self.C)
+            seed = self.location + self.scale * r
+            yield seed
+
+
+class ChiDistribution(Distribution):
+    """Class for Chi distribution."""
+
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+def CobbDouglasDistribution(**parameters):
+    """
+    Cobb-Douglas distribution is an alias of Lognormal distribution."""
+    return LogNormalDistribution(**parameters)
+
+
+def DoubleExponentialDistribution(**parameters):
+    """
+    Double Exponential distribution is an alias of Laplace distribution."""
+    return LaplaceDistribution(**parameters)
+
+
+class DoubleGammaDistribution(Distribution):
+    """
+    Double Gamma distribution is the signed version of Gamma distribution.
+    """
+    def __init__(self, location, scale, shape):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location:
+        @param scale:
+        @param shape:"""
+        self.location = location
+        self.scale = scale
+        self.shape = shape
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        r = nrpy.gammp(self.shape ,abs((x - self.location)/self.scale))
+        if x > self.location: return 0.5 + (0.5 * r)
+        else: return 0.5 - (0.5 * r)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution
+        from x-h to x+h for continuous distribution."""
+        r = math.exp(-1 * abs((x - self.location)/self.scale))
+        r = r * (abs((x - self.location)/self.scale) ** (self.shape -1))
+        return r / (2 * self.scale * nrpy.gammln(self.shape))
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.location
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 0.0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.shape * (self.shape + 1) * (self.scale ** 2)
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        return 0.5
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class DoubleWeibullDistribution(Distribution):
+    """
+    Double Weibull distribution is the signed version of Weibull distribution.
+    """
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class ExtremeLBDistribution(Distribution):
+    """Class for Extreme LB distribution."""
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class FiskDistribution(Distribution):
+    """Class for Fisk distribution."""
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+def FisherTippettDistribution(location, scale):
+    """
+    Fisher-Tippett distribution is an alias of Gumbel distribution.
+
+    @param location: S{eta}
+    @param scale: S{theta}"""
+    return GumbelDistribution(location, scale)
+
+
+class FoldedNormalDistribution(Distribution):
+    """Class for Folded Normal distribution."""
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class GenLogisticDistribution(Distribution):
+    """
+    Generalized Logistic distribution is a generalization of Logistic
+    distribution. It becomes Logistic distribution when shape = 1
+    """
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+def GompertzDistribution(location, scale):
+    """
+    Gompertz distribution is an alias of Gumbel distribution.
+
+    @param location: S{eta}
+    @param scale: S{theta}"""
+    return GumbelDistribution(location, scale)
+
+
+class GumbelDistribution(Distribution):
+    """Class for Gumbel Distribution."""
+
+    def __init__(self, location, scale):
+        """Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location: S{eta}
+        @param scale: S{theta}"""
+        self.location = location
+        self.scale = scale
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return math.exp(-1 * math.exp((self.location - x) / self.scale))
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return (1/self.scale) * math.exp((self.location - x) / self.scale) * \
+            self.CDF(x)
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.location + (GAMMA * self.scale)
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.location
+
+    def median(self):
+        """Gives the median of the sample."""
+        return self.location - self.scale * math.log10(math.log10(2))
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return 2.4
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 1.1395
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return 1.667 * ((PI * self.scale) ** 2)
+
+    def quantile1(self):
+        """Gives the 1st quantile of the sample."""
+        return self.location - self.scale * math.log10(math.log10(4))
+
+    def quantile3(self):
+        """Gives the 3rd quantile of the sample."""
+        return self.location - self.scale * math.log10(math.log10(1.333))
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        return 0.5704
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return 0.3679
+
+    def random(self, seed):
+        """Gives a random number based on the distribution."""
+        while 1:
+            seed = self.location - \
+                    (self.scale * math.log10(-1 * math.log10(seed)))
+            yield seed
+
+
+class HalfNormalDistribution(Distribution):
+    """
+    Half Normal distribution is a special case of Chi distribution where
+    shape (also degrees of freedom) = 1, and Folded Normal distribution
+    where location = 0
+    """
+    def __init__(self, **parameters):
+        """Constructor method. The parameters are used to construct the
+        probability distribution."""
+        try: self.distribution = ChiDistribution(location =
+                                                    parameters['location'],
+                                                 scale = parameters['scale'],
+                                                 shape = 1)
+        except KeyError:
+            raise DistributionParameterError('Halfnormal distribution \
+            requires location and scale parameters')
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return self.distribution.CDF(x)
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return self.distribution.PDF(x)
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        return self.distribution.inverseCDF(probability, start, step)
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.distribution.mean()
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.distribution.mode()
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return self.distribution.kurtosis()
+    def skew(self):
+        """Gives the skew of the sample."""
+        return self.distribution.skew()
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.distribution.variance()
+
+
+class HyperbolicSecantDistribution(Distribution):
+    """Class for Hyperbolic Secant Distribution."""
+
+    def __init__(self, location, scale):
+        """
+        Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location:
+        @param scale:"""
+        self.location = location
+        self.scale = scale
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return (2 / PI) * \
+            (1 / math.tan(math.exp((x - self.location) / self.scale)))
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution
+        from x-h to x+h for continuous distribution."""
+        return (1 / math.cosh((x - self.location) / self.scale)) / \
+                (PI * math.scale)
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.location
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.location
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return 2.0
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 0.0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return 0.25 * ((PI * self.scale) ** 2)
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        return 0.5
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return 0.5
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class LaplaceDistribution(Distribution):
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class LogisticDistribution(Distribution):
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+def LogLogisticDistribution(**parameters):
+    """
+    Log-Logistic distribution is an alias of Fisk distribution."""
+    return FiskDistribution(**parameters)
+
+
+class LogNormalDistribution(Distribution):
+    def __init__(self, a, b):
+        """Constructor method. The parameters are used to construct the
+        probability distribution."""
+        self.location = a
+        self. scale = b
+        if (b ** 2) < 0:
+            raise AttributeError
+#    def CDF(self, x):
+#        """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0
+#        to a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return math.exp((self.location + (self.scale ** 2) * self.location*(-1)))
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+    def random(self):
+        """Gives a random number based on the distribution."""
+        return random.lognormalvariate(self.location, self.scale)
+
+
+def LogWeibullDistribution(location, scale):
+    """
+    Log-Weibull distribution is an alias of Gumbel distribution.
+
+    @param location: S{eta}
+    @param scale: S{theta}"""
+    return GumbelDistribution(location, scale)
+
+
+def LorentzDistribution(**parameters):
+    """
+    Lorentz distribution is an alias of Cauchy distribution."""
+    return CauchyDistribution(**parameters)
+
+
+class MaxwellDistribution(Distribution):
+    """
+    Maxwell distribution is a special case of Chi distribution where
+    location = 0 and shape (degrees of freedom) = 3
+    """
+
+    def __init__(self, scale):
+        """
+        Constructor method.
+
+        @param scale:"""
+        self.distribution = ChiDistribution(0, scale, 3)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return self.distribution.CDF(x)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return self.distribution.PDF(x)
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        the corresponding value on the x-axis."""
+        return self.distribution.inverseCDF(probability, start, step)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.distribution.mean()
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.distribution.mode()
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return self.distribution.kurtosis()
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return self.distribution.skew()
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.distribution.variance()
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        return self.distribution.random()
+
+
+class NakagamiDistribution(Distribution):
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class NegativeBinomialDistribution(Distribution):
+    """Class for Negative Binomial Distribution."""
+
+    def __init__(self, success, target):
+        """Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param success: probability of success; 0 <= success <= 1
+        @param target: a constant, target number of successes"""
+        self.success = success
+        self.target = target
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        summation = 0.0
+        for i in range(x): summation = summation + self.PDF(i)
+        return summation
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return nrpy.bico(x - 1, self.target - 1) * \
+                (self.success ** self.target) * \
+                ((1 - self.success) ** (x - self.target))
+
+    def inverseCDF(self, probability, start = 0, step = 1):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.target / self.success
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return int((self.success + self.target - 1)/self.success)
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+def NegativeExponentialDistribution(**parameters):
+    """
+    Negative-exponential distribution is an alias of Exponential distribution."""
+    return ExponentialDistribution(**parameters)
+
+
+class ParetoDistribution(Distribution):
+    """Class for Pareto Distribution."""
+
+    def __init__(self, location=1.0, scale=1.0):
+        """Constructor method. The parameters are used to construct the
+        probability distribution.
+
+        @param location: also the scale; default = 1.0
+        @param scale: S{lambda}; default = 1.0"""
+        self.location = location
+        self.scale = scale
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return 1 - (self.location/x) ** self.scale
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return (self.shape * (self.location ** self.scale)) / \
+                (x ** (self.scale + 1))
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return (self.location * self.scale) / (self.scale - 1)
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.location
+
+    def median(self):
+        """Gives the median of the sample."""
+        return self.location * (2 ** (1/self.scale))
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        n = 6 * (self.scale ** 3 + self.scale ** 2 + 6 * self.scale - 2)
+        d = self.scale * (self.scale ** 2 - 7 * self.scale + 12)
+        return n/d
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        n = 2 * (self.scale + 1) * math.sqrt(self.scale - 2)
+        d = (self.scale - 3) * math.sqrt(self.scale)
+        return n/d
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        n = (self.location ** 2) * self.scale
+        d = (self.scale - 2) * ((self.scale - 1) ** 2)
+        return n/d
+
+    def quantile1(self):
+        """Gives the 1st quantile of the sample."""
+        return self.location * (1.333 ** (1/self.scale))
+
+    def quantile3(self):
+        """Gives the 3rd quantile of the sample."""
+        return self.location * (4 ** (1/self.scale))
+
+    def qmean(self):
+        """Gives the quantile of the arithmetic mean of the sample."""
+        return 1 - (((self.scale - 1) / self.scale) ** self.scale)
+
+    def qmode(self):
+        """Gives the quantile of the mode of the sample."""
+        return 0.0
+
+    def random(self):
+        """Gives a random number based on the distribution."""
+        return random.paretovariate(self.scale)
+
+
+class PascalDistribution(Distribution):
+    """
+    Class for Pascal Distribution. Pascal Distribution is a form of Negative
+    Binomial Distribution where the 'target' is an integer
+    """
+    def __init__(self, success, target):
+        """Constructor method.
+
+        @param success: probability of success; 0 <= success <= 1
+        @param target: a constant, target number of successes"""
+        self.distribution = NegativeBinomialDistribution(success, int(target))
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return self.distribution.CDF(x)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return self.distribution.PDF(x)
+
+    def inverseCDF(self, probability, start = 0.0, step =0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        return self.distribution.inverseCDF(probability, start, step)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.distribution.mean()
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.distribution.mode()
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return self.distribution.kurtosis()
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return self.distribution.skew()
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.distribution.variance()
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        return self.distribution.random()
+
+
+def PolyaDistribution(success, target):
+    """
+    Polya distribution is an alias of Negative Binomial distribution.
+
+    @param success: probability of success; 0 <= success <= 1
+    @param target: a constant, target number of successes
+    """
+    return NegativeBinomialDistribution(success, target)
+
+
+class PowerFunctionDistribution(Distribution):
+    """
+    Class for Power Function Distribution. It is a form of Beta Distribution.
+    """
+
+    def __init__(self, shape):
+        """Constructor method.
+
+        @param shape:
+        """
+        self.distribution = BetaDistribution(0, 1, shape, 1)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return self.distribution.CDF(x)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return self.distribution.PDF(x)
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        return self.distribution.inverseCDF(probability, start, step)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.distribution.mean()
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.distribution.mode()
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return self.distribution.kurtosis()
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return self.distribution.skew()
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.distribution.variance()
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        return self.distribution.random()
+
+
+class RademacherDistribution(Distribution):
+    """Class for Rademacher Distribution."""
+
+    def __init__(self):
+        """Constructor method."""
+        pass
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        if x < -1:
+            return 0.0
+        elif x > -1 and x < 1:
+            return 0.5
+        else: return 1.0
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution
+        from x-h to x+h for continuous distribution."""
+        if x == -1 or x == 1: return 0.5
+        else: return 0.0
+
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        if probability == 0.0: return (-1.0001, 0.0)
+        if probability == 1.0: return (1.0, 1.0)
+        else: return (0.999, 0.5)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return 0
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return 0
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return 1
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+class RayleighDistribution(Distribution):
+    """
+    Rayleigh distribution is a special case of Chi distribution where
+    location = 0 and shape (degrees of freedom) = 2
+    """
+
+    def __init__(self, scale):
+        """Constructor method.
+
+        @param scale:"""
+        self.distribution = ChiDistribution(0, scale, 2)
+
+    def CDF(self, x):
+        """
+        Cummulative Distribution Function, which gives the cummulative
+        probability (area under the probability curve) from -infinity or 0 to
+        a give x-value on the x-axis where y-axis is the probability."""
+        return self.distribution.CDF(x)
+
+    def PDF(self, x):
+        """
+        Partial Distribution Function, which gives the probability for the
+        particular value of x, or the area under probability distribution from
+        x-h to x+h for continuous distribution."""
+        return self.distribution.PDF(x)
+
+    def inverseCDF(self, probability, start = 0.0, step =0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value and
+        returns the corresponding value on the x-axis."""
+        return self.distribution.inverseCDF(probability, start, step)
+
+    def mean(self):
+        """Gives the arithmetic mean of the sample."""
+        return self.distribution.mean()
+
+    def mode(self):
+        """Gives the mode of the sample."""
+        return self.distribution.mode()
+
+    def kurtosis(self):
+        """Gives the kurtosis of the sample."""
+        return self.distribution.kurtosis()
+
+    def skew(self):
+        """Gives the skew of the sample."""
+        return self.distribution.skew()
+
+    def variance(self):
+        """Gives the variance of the sample."""
+        return self.distribution.variance()
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        return self.distribution.random()
+
+
+class ReciprocalDistribution(Distribution):
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+    def inverseCDF(self, probability, start=0.0, step=0.01):
+        """
+        It does the reverse of CDF() method, it takes a probability value
+        and returns the corresponding value on the x-axis."""
+        cprob = self.CDF(start)
+        if probability < cprob: return (start, cprob)
+        while (probability > cprob):
+            start = start + step
+            cprob = self.CDF(start)
+            # print start, cprob
+        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+#    def random(self):
+#        """Gives a random number based on the distribution."""
+#        raise DistributionFunctionError
+
+
+def RectangularDistribution(**parameters):
+    """
+    Rectangular distribution is an alias of Uniform distribution."""
+    return UniformDistribution(**parameters)
+
+
+def SechSquaredDistribution(**parameters):
+    """
+    Sech-squared distribution is an alias of Logistic distribution."""
+    return LogisticDistribution(**parameters)
+
+
+def WaldDistribution(**parameters):
+    """
+    Wald distribution is an alias of Inverse Normal distribution."""
+    return InverseNormalDistribution(**parameters)
+
+
+#class DummyDistribution(Distribution):
+#    def __init__(self, **parameters):
+#        """Constructor method. The parameters are used to construct the
+#            probability distribution."""
+#        raise DistributionFunctionError
+#    def CDF(self, x):
+#       """
+#        Cummulative Distribution Function, which gives the cummulative
+#        probability (area under the probability curve) from -infinity or 0 to
+#        a give x-value on the x-axis where y-axis is the probability."""
+#        raise DistributionFunctionError
+#    def PDF(self, x):
+#        """
+#        Partial Distribution Function, which gives the probability for the
+#        particular value of x, or the area under probability distribution
+#        from x-h to x+h for continuous distribution."""
+#        raise DistributionFunctionError
+#    def inverseCDF(self, probability, start=0.0, step=0.01):
+#        """
+#        It does the reverse of CDF() method, it takes a probability value
+#        and returns the corresponding value on the x-axis."""
+#        cprob = self.CDF(start)
+#        if probability < cprob: return (start, cprob)
+#        while (probability > cprob):
+#            start = start + step
+#            cprob = self.CDF(start)
+#            # print start, cprob
+#        return (start, cprob)
+#    def mean(self):
+#        """Gives the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def mode(self):
+#        """Gives the mode of the sample."""
+#        raise DistributionFunctionError
+#    def kurtosis(self):
+#        """Gives the kurtosis of the sample."""
+#        raise DistributionFunctionError
+#    def skew(self):
+#        """Gives the skew of the sample."""
+#        raise DistributionFunctionError
+#    def variance(self):
+#        """Gives the variance of the sample."""
+#        raise DistributionFunctionError
+#    def quantile1(self):
+#        """Gives the 1st quantile of the sample."""
+#        raise DistributionFunctionError
+#    def quantile3(self):
+#        """Gives the 3rd quantile of the sample."""
+#        raise DistributionFunctionError
+#    def qmean(self):
+#        """Gives the quantile of the arithmetic mean of the sample."""
+#        raise DistributionFunctionError
+#    def qmode(self):
+#        """Gives the quantile of the mode of the sample."""
+#        raise DistributionFunctionError
+##    def random(self, seed):
+##        """Gives a random number based on the distribution."""
+##        while 1:
+##            func
+##            yield seed
diff --git a/examples/genetic_01.py b/examples/genetic_01.py
new file mode 100644
index 0000000..12cde2f
--- /dev/null
+++ b/examples/genetic_01.py
@@ -0,0 +1,20 @@
+######################################################################
+# GA Program #1: Simulates different rates of mutation (5 to 25% above
+# background mutation rate. Fitness is the average of the chromosome
+# where 1-4 is allowed; hence, all 4 organisms should converge to the
+# average of 2.5 but at different rates.
+######################################################################
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+    
+import genetic as g
+c = g.Chromosome([0]*200, [1,2,3,4])
+o = g.Organism([c])
+oset = [o.clone() for x in range(5)]
+print('Generation \tMutation rate above background rate')
+print('Count \t\t', '\t'.join(['5%', '10%', '15%', '20%', '25%']))
+print('=' * 60)
+for x in range(100):
+    print(x,'\t\t', '\t'.join([str(oset[x].fitness()) for x in range(5)]))
+    for mut in range(1, 6):
+        oset[mut-1].mutation_scheme('point', float(mut)/20)
diff --git a/examples/genetic_02.py b/examples/genetic_02.py
new file mode 100644
index 0000000..cc8e836
--- /dev/null
+++ b/examples/genetic_02.py
@@ -0,0 +1,18 @@
+######################################################################
+# GA Program #2: Simulates crossover events between 2 chromosomes
+######################################################################
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import genetic as g
+c1 = g.Chromosome([0]*50, [1,2,3,4])          # integer chromosome
+c2 = g.Chromosome(['A']*50, ['A','B','C','D'])  # alphabet chromosome
+print('INITIAL CHROMOSOMES ==================================')
+print('Integer chromosome sequence: ' + str(c1.sequence))
+print('Alphabet chromosome sequence: ' + str(c2.sequence))
+for x in range(10, 50, 10):
+    print()
+    (g1, g2) = g.crossover(c1, c2, x)
+    print('Crossover at base ' + str(x))
+    print('Integer chromosome sequence: ' + str(g1.sequence))
+    print('Alphabet chromosome sequence: ' + str(g2.sequence))
diff --git a/examples/genetic_03.py b/examples/genetic_03.py
new file mode 100644
index 0000000..0d83b07
--- /dev/null
+++ b/examples/genetic_03.py
@@ -0,0 +1,13 @@
+######################################################################
+# GA Program #3: Simulates an initial population of 200 organisms to 
+# reach the goal of having all 4s for their genome
+######################################################################
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import genetic as g
+
+g.population_data['maximum_generation'] = 1000
+g.population_data['chromosome'] = [0] * 200
+pop = g.population_constructor(g.population_data)
+g.population_simulate(pop, 100, 'never', 'pop', 0.1, 'result.txt')
diff --git a/examples/genetic_04.py b/examples/genetic_04.py
new file mode 100644
index 0000000..f4c9177
--- /dev/null
+++ b/examples/genetic_04.py
@@ -0,0 +1,37 @@
+######################################################################
+# GA Program #5: Simulates an initial population of 200 organisms to 
+# reach the goal of having all 4s for their genome, from genome size
+# of 200 to 1500 in 100 base increments
+######################################################################
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import genetic as g
+
+pdata = \
+{
+    'nucleotide_list' : [1, 2, 3, 4],
+    'chromosome_length' : 200,
+    'chromosome_type' : 'defined',
+    'chromosome' : [1] * 200,
+    'background_mutation' : 0.0001,
+    'genome_size' : 1,
+    'population_size' : 200,
+    'fitness_function' : 'default',
+    'mutation_scheme' : 'default',
+    'additional_mutation_rate' : 0.01,
+    'mutation_type' : 'point',
+    'goal' : 4,
+    'maximum_generation' : 5000,
+    'prepopulation_control' : 'default',
+    'mating' : 'default',
+    'postpopulation_control' : 'default',
+    'generation_events' : 'default',
+    'report' : 'default'
+}
+
+for chromosome_length in range(200, 1600, 100):
+    pdata['chromosome'] = [0] * chromosome_length
+    pop = g.population_constructor(pdata)
+    g.population_simulate(pop, 100, 'never', 'pop', 0.1, 
+                          str(chromosome_length)+'result.txt')
diff --git a/examples/lindenmayer_dragon.py b/examples/lindenmayer_dragon.py
new file mode 100644
index 0000000..85cc80b
--- /dev/null
+++ b/examples/lindenmayer_dragon.py
@@ -0,0 +1,25 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import random
+from lindenmayer import lindenmayer
+
+axiom = 'FX'
+rules = [['X', 'XRYFR'], ['Y', 'LFXLY']]
+start_position = (150, 150)
+iterations = 12
+turtle_file = '19_lindenmayer_dragon_turtle.py'
+image_file = '19_lindenmayer_dragon_turtle.svg'
+mapping = {'set_angle': 90,
+           'random_angle': 0,
+           'set_distance': 5,
+           'random_distance': 0,
+           'background_colour': 'wheat1',
+           'F': 'forward',
+           'R': 'right',
+           'L': 'left'}
+
+l = lindenmayer(1)
+l.add_rules(rules)
+l.generate(axiom, iterations)
+l.turtle_generate(turtle_file, image_file, start_position, mapping)
diff --git a/examples/lindenmayer_koch_curve.py b/examples/lindenmayer_koch_curve.py
new file mode 100644
index 0000000..0d41e91
--- /dev/null
+++ b/examples/lindenmayer_koch_curve.py
@@ -0,0 +1,25 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import random
+from lindenmayer import lindenmayer
+
+axiom = 'F'
+rules = [['F', 'FRFLFLFRF']]
+start_position = (-300, 250)
+iterations = 5
+turtle_file = '19_lindenmayer_koch_turtle.py'
+image_file = '19_lindenmayer_koch_turtle.svg'
+mapping = {'set_angle': 90,
+           'random_angle': 0,
+           'set_distance': 2.5,
+           'random_distance': 0,
+           'background_colour': 'pale goldenrod',
+           'F': 'forward',
+           'R': 'right',
+           'L': 'left'}
+
+l = lindenmayer(1)
+l.add_rules(rules)
+l.generate(axiom, iterations)
+l.turtle_generate(turtle_file, image_file, start_position, mapping)
diff --git a/examples/lindenmayer_plant.py b/examples/lindenmayer_plant.py
new file mode 100644
index 0000000..1b762a2
--- /dev/null
+++ b/examples/lindenmayer_plant.py
@@ -0,0 +1,33 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import random
+from lindenmayer import lindenmayer
+
+axiom = 'X'
+rules = [['X', '0FL[2[X]R3X]R1F[3RFX]LX'],
+         ['F', 'FF']]
+start_position = (0, -200)
+iterations = 6
+turtle_file = '19_lindenmayer_plant_turtle.py'
+image_file = '19_lindenmayer_plant_turtle.svg'
+mapping = {'set_angle': 25,
+           'random_angle': 0,
+           'set_distance': 2.5,
+           'random_distance': 0,
+           'set_heading': 90,
+           'background_colour': 'azure',
+           'F': 'forward',
+           'R': 'right',
+           'L': 'left',
+           '[': 'push',
+           ']': 'pop',
+           '0': 'brown',
+           '1': 'dark green',
+           '2': 'forest green'
+           }
+
+l = lindenmayer(1)
+l.add_rules(rules)
+l.generate(axiom, iterations)
+l.turtle_generate(turtle_file, image_file, start_position, mapping)
diff --git a/examples/lindenmayer_sierpinski_triangle.py b/examples/lindenmayer_sierpinski_triangle.py
new file mode 100644
index 0000000..6d44e91
--- /dev/null
+++ b/examples/lindenmayer_sierpinski_triangle.py
@@ -0,0 +1,27 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import random
+from lindenmayer import lindenmayer
+
+axiom = 'A'
+rules = [['A', 'BLALB'],
+         ['B', 'ARBRA']]
+start_position = (-300, 250)
+iterations = 8
+turtle_file = '19_lindenmayer_sierpinski_turtle.py'
+image_file = '19_lindenmayer_sierpinski_turtle.svg'
+mapping = {'set_angle': 60,
+           'random_angle': 0,
+           'set_distance': 2.5,
+           'random_distance': 0,
+           'background_colour': 'RoyalBlue1',
+           'A': 'forward',
+           'B': 'forward',
+           'R': 'right',
+           'L': 'left'}
+
+l = lindenmayer(1)
+l.add_rules(rules)
+l.generate(axiom, iterations)
+l.turtle_generate(turtle_file, image_file, start_position, mapping)
diff --git a/examples/lindenmayer_tree.py b/examples/lindenmayer_tree.py
new file mode 100644
index 0000000..b8b707c
--- /dev/null
+++ b/examples/lindenmayer_tree.py
@@ -0,0 +1,32 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import random
+from lindenmayer import lindenmayer
+
+axiom = 'F'
+rules = [['F', '0FFL[1LFRFRF]R[2RFLFLF]']]
+start_position = (0, -200)
+iterations = 5
+turtle_file = '19_lindenmayer_tree_turtle.py'
+image_file = '19_lindenmayer_tree_turtle.svg'
+mapping = {'set_angle': 22,
+           'random_angle': 0,
+           'set_distance': 5,
+           'random_distance': 0,
+           'set_heading': 90,
+           'background_colour': 'ivory',
+           'F': 'forward',
+           'R': 'right',
+           'L': 'left',
+           '[': 'push',
+           ']': 'pop',
+           '0': 'brown',
+           '1': 'dark green',
+           '2': 'forest green'
+           }
+
+l = lindenmayer(1)
+l.add_rules(rules)
+l.generate(axiom, iterations)
+l.turtle_generate(turtle_file, image_file, start_position, mapping)
diff --git a/examples/ode_boundary_system.py b/examples/ode_boundary_system.py
new file mode 100644
index 0000000..30b2364
--- /dev/null
+++ b/examples/ode_boundary_system.py
@@ -0,0 +1,40 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import ode
+
+birth = 0               # birth rate
+death = 0.0001          # natural death percent (per day)
+transmission = 0.0095   # transmission percent  (per day)
+resurect = 0.0001       # resurect percent (per day)
+destroy = 0.0001        # destroy percent  (per day)
+
+def human(t, y):
+    infected = transmission*y[0]*y[1]
+    dead = death*y[0]
+    return birth - infected - dead
+def zombie(t, y):
+    newly_infected = transmission*y[0]*y[1]
+    resurrected = resurect*y[2]
+    destroyed = destroy*y[0]*y[1]
+    return newly_infected + resurrected - destroyed
+def dead(t, y):
+    natural_death = death*y[0]
+    destroyed_zombies = destroy*y[0]*y[1]
+    created_zombies = resurect*y[2]
+    return natural_death + destroyed_zombies - created_zombies
+    
+f = [human, zombie, dead]   # system of ODEs
+# initial human, zombie, death population, and total respectively
+y = [500.0, 0, 0]  
+lower_bound = {0: [10.0, 10.0]}
+upper_bound = {1: [1000.0, 1000.0]}
+
+print('Solving using 5th order Dormand-Prince method ......')
+nobound = [x for x in ode.DP5(f, 0.0, y, 0.1, 50.0)]
+lowerbound = [x for x in ode.DP5(f, 0.0, y, 0.1, 50.0, None, lower_bound)]
+doublebound = [x for x in ode.DP5(f, 0.0, y, 0.1, 50.0, None,
+                                  lower_bound, upper_bound)]
+for i in range(len(nobound)):
+    consolidated = nobound[i] + lowerbound[i][1:] + doublebound[i][1:]
+    print ','.join([str(x) for x in consolidated])
diff --git a/examples/ode_bounded_variables.py b/examples/ode_bounded_variables.py
new file mode 100644
index 0000000..4a8f9cc
--- /dev/null
+++ b/examples/ode_bounded_variables.py
@@ -0,0 +1,95 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import ode
+
+# --------------------------
+# Case 1: Bounded Variables
+# --------------------------
+
+# birth rate
+birth = 0    
+# natural death percent (per day)    
+death = 0.0001 
+# transmission percent  (per day)
+transmission = 0.0095  
+# resurect percent (per day) 
+resurrect = 0.0001    
+# destroy percent  (per day)   
+destroy = 0.0001        
+
+def human1(t, y):
+    infected = transmission * y[0] * y[1]
+    dead = death * y[0]
+    return birth - infected - dead
+def zombie1(t, y):
+    newly_infected = transmission * \
+        y[0] * y[1]
+    resurrected = resurrect * y[2]
+    destroyed = destroy * y[0] * y[1]
+    return newly_infected + resurrected - \
+           destroyed
+def dead1(t, y):
+    natural_death = death * y[0]
+    destroyed_zombies = destroy * \
+        y[0] * y[1]
+    created_zombies = resurrect * y[2]
+    return natural_death + \
+           destroyed_zombies - \
+           created_zombies
+
+# system of ODEs
+bounded = [human1, zombie1, dead1]   
+
+# initial human, zombie, death population 
+# respectively
+y = [500.0, 0, 0]  
+print('''Solving using 5th order Dormand-Prince method with \
+bounded variables......''')
+for i in [x for x in 
+          ode.DP5(bounded, 0.0, y, 0.1, 50.0)]:
+    print(','.join([str(z) for z in i]))
+    
+# -----------------------------
+# Case 2: Un-Bounded Variables
+# -----------------------------
+
+def human2(t, y):
+    infected = y[5] * y[0] * y[1]
+    dead = y[4] * y[0]
+    return y[3] - infected - dead
+def zombie2(t, y):
+    newly_infected = y[5] * y[0] * y[1]
+    resurrected = y[6] * y[2]
+    destroyed = y[7] * y[0] * y[1]
+    return newly_infected + resurrected - \
+           destroyed
+def dead2(t, y):
+    natural_death = y[4] * y[0]
+    destroyed_zombies = y[7] * y[0] * y[1]
+    created_zombies = y[6] * y[2]
+    return natural_death + \
+           destroyed_zombies - \
+           created_zombies
+
+unbounded = range(8)
+unbounded[0] = human2
+unbounded[1] = zombie2
+unbounded[2] = dead2
+
+y = range(8)
+y[0] = 500.0 # initial human population
+y[1] = 0.0   # initial zombie population
+y[2] = 0.0   # initial death population
+y[3] = 0     # birth rate             
+y[4] = 0.0001 # natural death percent / day
+y[5] = 0.0095 # transmission percent  / day
+y[6] = 0.0001  # resurrect percent / day
+y[7] = 0.0001  # destroy percent / day
+
+print('''Solving using 5th order Dormand-Prince method \
+with un-bounded variables......''')
+for i in [x for x in 
+          ode.DP5(unbounded, 0.0, y, 0.1, 50.0)]:
+    print(','.join([str(z) for z in i]))
+
diff --git a/examples/ode_manual.py b/examples/ode_manual.py
new file mode 100644
index 0000000..2c430dc
--- /dev/null
+++ b/examples/ode_manual.py
@@ -0,0 +1,43 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import ode
+
+birth = 0               # birth rate
+death = 0.0001          # natural death percent (per day)
+transmission = 0.0095   # transmission percent  (per day)
+resurect = 0.0002       # resurect percent (per day)
+destroy = 0.0003        # destroy percent  (per day)
+
+              
+def human(t, y):
+    infected = transmission*y[0]*y[1]
+    dead = death*y[0]
+    return birth - infected - dead
+def zombie(t, y):
+    newly_infected = transmission*y[0]*y[1]
+    resurrected = resurect*y[2]
+    destroyed = destroy*y[0]*y[1]
+    return newly_infected + resurrected - destroyed
+def dead(t, y):
+    natural_death = death*y[0]
+    destroyed_zombies = destroy*y[0]*y[1]
+    created_zombies = resurect*y[2]
+    return natural_death + destroyed_zombies - created_zombies
+
+def modifying_expression(y, step):
+    y[0] = y[0] + (5 * step)
+    return y
+  
+lower_bound = {'2': [0.0, 0.0]} 
+upper_bound = {'0': [700, 700]} 
+
+f = [human, zombie, dead]   # system of ODEs
+
+y = [500.0, 0, 0]  # initial human, zombie, death population respectively
+print('Solving using 4th order Runge Kutta method ......')
+for i in [x for x in ode.RK4(f, 0.0, y, 0.1, 100.0, modifying_expression,
+                             lower_bound, upper_bound)]:
+    print(','.join([str(z) for z in i]))
+print('')
+
diff --git a/examples/ode_modifying_function.py b/examples/ode_modifying_function.py
new file mode 100644
index 0000000..b04974b
--- /dev/null
+++ b/examples/ode_modifying_function.py
@@ -0,0 +1,43 @@
+import random
+
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import ode
+
+birth = 0               # birth rate
+death = 0.0001          # natural death percent (per day)
+transmission = 0.0095   # transmission percent  (per day)
+resurect = 0.0001       # resurect percent (per day)
+destroy = 0.0001        # destroy percent  (per day)
+
+def human(t, y):
+    infected = transmission*y[0]*y[1]
+    dead = death*y[0]
+    return birth - infected - dead
+def zombie(t, y):
+    newly_infected = transmission*y[0]*y[1]
+    resurrected = resurect*y[2]
+    destroyed = destroy*y[0]*y[1]
+    return newly_infected + resurrected - destroyed
+def dead(t, y):
+    natural_death = death*y[0]
+    destroyed_zombies = destroy*y[0]*y[1]
+    created_zombies = resurect*y[2]
+    return natural_death + destroyed_zombies - created_zombies
+
+def influx(y, step):
+    y[0] = y[0] + (5*step)
+    return y
+    
+f = [human, zombie, dead]   # system of ODEs
+# initial human, zombie, death population, and total respectively
+y = [500.0, 0, 0]  
+
+print('Solving using 5th order Dormand-Prince method ......')
+noinflux = [x for x in ode.DP5(f, 0.0, y, 0.1, 50.0)]
+influx = [x for x in ode.DP5(f, 0.0, y, 0.1, 50.0, influx)]
+
+for i in range(len(noinflux)):
+    consolidated = noinflux[i] + influx[i][1:]
+    print(','.join([str(x) for x in consolidated]))
diff --git a/examples/ode_radioactive.py b/examples/ode_radioactive.py
new file mode 100644
index 0000000..c6a04f2
--- /dev/null
+++ b/examples/ode_radioactive.py
@@ -0,0 +1,65 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import ode
+
+initial_nuclei = 10000.0
+decay_constant = 0.2
+
+def decay(t, y):
+    return -decay_constant * y[0]
+
+print('Solving using Euler method......')
+for i in [x for x in ode.Euler([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print('Solving using RK4 method......')
+for i in [x for x in ode.RK4([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print('Solving using RK3 method......') 
+for i in [x for x in ode.RK3([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print("Solving using Heun's method......")
+for i in [x for x in ode.Heun([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print('Solving using RK4 method with 3/8 rule......')
+for i in [x for x in ode.RK38([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print('Solving using CK4 method......')
+for i in [x for x in ode.CK4([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print('Solving using CK5 method......') 
+for i in [x for x in ode.CK5([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print('Solving using RKF4 method......')
+for i in [x for x in ode.RKF4([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print('Solving using RKF5 method......')
+for i in [x for x in ode.RKF5([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print('Solving using DP4 method......') 
+for i in [x for x in ode.DP4([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
+
+print('Solving using DP5 method......')
+for i in [x for x in ode.DP5([decay], 0.0, [initial_nuclei], 0.1, 50.0)]:
+    print(','.join([str(x) for x in i]))
+print('')
diff --git a/examples/ode_zombie_construct.py b/examples/ode_zombie_construct.py
new file mode 100644
index 0000000..667e8d1
--- /dev/null
+++ b/examples/ode_zombie_construct.py
@@ -0,0 +1,38 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+from ode import ODE_constructor
+
+scriptfile = 'zombie_attack.py'
+resultsfile = 'zombie_data.csv'
+time = (0.0, 0.1, 100.0)
+ODE_solver = 'RK4'
+expressions = {'human': ['birth_rate',
+                         '- (transmission_rate * human * zombie)',
+                         '- (death_rate * human)'],
+               'zombie': ['(transmission_rate * human * zombie)',
+                          '(resurrection_rate * dead)',
+                          '- (destroy_rate * human * zombie)'],
+               'dead': ['(death_rate * human)',
+                        '(destroy_rate * human * zombie)',
+                        '- (resurrection_rate * dead)']}
+parameters = {'birth_rate': 0.0,          # birth rate
+              'transmission_rate': 0.0095,# transmission percent  (per day)
+              'death_rate': 0.0001,       # natural death percent (per day)
+              'resurrection_rate': 0.0002,# resurect percent (per day)
+              'destroy_rate':0.0003       # destroy percent  (per day)
+              }
+initial_conditions = {'human': 500.0,
+                      'zombie': 0.0, 
+                      'dead': 0.0}
+modifying_expressions = ['human = human + (5*step)']
+lower_bound = {'human': [0.0, 0.0]}
+upper_bound = {'zombie': [700, 700]}
+overflow = 1e100
+zerodivision = 1e100
+                    
+s = ODE_constructor(scriptfile, resultsfile, time, ODE_solver,
+                    expressions, parameters, initial_conditions,
+                    modifying_expressions,
+                    lower_bound, upper_bound,
+                    overflow, zerodivision)
diff --git a/examples/pnet_bake_bread.py b/examples/pnet_bake_bread.py
new file mode 100644
index 0000000..c4d5911
--- /dev/null
+++ b/examples/pnet_bake_bread.py
@@ -0,0 +1,64 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import pnet
+
+net = pnet.PNet()
+
+# The ingredients
+net.add_places('flour', {'flour': 1000})
+net.add_places('water', {'water': 500})
+net.add_places('sugar', {'sugar': 20})
+net.add_places('yeast', {'yeast': 1})
+
+# The "utensils"
+net.add_places('mixer', {'flour': 0, 'water': 0, 'sugar': 0, 
+                         'yeast': 0, 'dough': 0})
+net.add_places('pan', {'dough': 0})
+net.add_places('oven', {'dough': 0, 'bread': 0})
+net.add_places('table', {'bread': 0, 'temperature': 400})
+net.add_places('air', {'heat': 0})
+
+# The steps
+net.add_rules('add_flour', 'step', ['flour.flour -> mixer.flour; 100'])
+net.add_rules('add_water', 'step', ['water.water -> mixer.water; 50'])
+net.add_rules('add_sugar', 'step', ['sugar.sugar -> mixer.sugar; 2'])
+net.add_rules('add_yeast', 'delay', ['yeast.yeast -> mixer.yeast; 0.5; 5'])
+net.add_rules('blend', 'step', ['mixer.flour -> mixer.dough; 80',
+                                'mixer.water -> mixer.dough; 40',
+                                'mixer.sugar -> mixer.dough; 1.5',
+                                'mixer.yeast -> mixer.dough; 1'])
+net.add_rules('rise', 'incubate', ['10; mixer.dough -> pan.dough; \
+                                    mixer.flour == 0; mixer.water == 0; \
+                                    mixer.sugar == 0; mixer.yeast == 0'])
+net.add_rules('set', 'incubate', ['10; pan.dough -> oven.dough; \
+                                   pan.dough > 0'])
+net.add_rules('bake', 'ratio', ['oven.dough -> oven.bread; 0.3; \
+                                 oven.dough < 1; 0'])
+net.add_rules('transfer', 'incubate', ['1; oven.bread -> table.bread; \
+                                        oven.dough == 0'])
+def cooling(places): 
+    temp = places['table'].attributes['temperature']
+    if temp <= 30.0: return 0.0
+    else: return 0.1 * temp
+net.add_rules('cool', 'function', ['table.temperature -> air.heat', 
+                                    cooling, 
+                                   'table.bread > 0'])
+
+# Bake the bread !!!
+net.simulate(90, 1, 1)
+
+from pprint import pprint
+pprint(net.rules)
+
+data = net.report_tokens()
+headers = ['timestep'] + data[0][1]
+
+f = open('bread.csv', 'w')
+f.write(','.join(headers) + '\n')
+for tdata in data:
+    tdata = [tdata[0]] + [str(x) for x in tdata[2]]
+    f.write(','.join(tdata) + '\n')
+f.close()
+
+
diff --git a/examples/pnet_sirs.py b/examples/pnet_sirs.py
new file mode 100644
index 0000000..4f0129e
--- /dev/null
+++ b/examples/pnet_sirs.py
@@ -0,0 +1,57 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import pnet
+
+infection = 0.01
+recovery = 0.005
+resusceptible = 0.01
+
+net = pnet.PNet()
+
+net.add_places('susceptible', {'susceptible': 100})
+net.add_places('infected', {'infected': 0})
+net.add_places('recovered', {'recovered': 0})
+
+def susceptible_infected(places): 
+    place = places['susceptible']
+    susceptible = place.attributes['susceptible']
+    return infection * susceptible
+    
+def infected_recovered(places):
+    place = places['infected']
+    infected = place.attributes['infected']
+    return recovery * infected
+    
+def recovered_susceptible(places):
+    place = places['recovered']
+    recovered = place.attributes['recovered']
+    return resusceptible * recovered
+    
+net.add_rules('infection', 'function', 
+              ['susceptible.susceptible -> infected.infected', 
+               susceptible_infected, 
+               'susceptible.susceptible > 0'])
+net.add_rules('recovery', 'function', 
+              ['infected.infected -> recovered.recovered', 
+               infected_recovered, 
+               'infected.infected > 0'])
+net.add_rules('resusceptible', 'function', 
+              ['recovered.recovered -> susceptible.susceptible', 
+               recovered_susceptible, 
+               'recovered.recovered > 0'])
+
+net.simulate(500, 1, 1)
+
+from pprint import pprint
+pprint(net.rules)
+
+data = net.report_tokens()
+headers = ['timestep'] + data[0][1]
+
+f = open('sirs.csv', 'w')
+f.write(','.join(headers) + '\n')
+for tdata in data:
+    tdata = [tdata[0]] + [str(x) for x in tdata[2]]
+    f.write(','.join(tdata) + '\n')
+f.close()
diff --git a/examples/pnet_sis.py b/examples/pnet_sis.py
new file mode 100644
index 0000000..61199dd
--- /dev/null
+++ b/examples/pnet_sis.py
@@ -0,0 +1,45 @@
+import sys, os
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+
+import pnet
+
+infection = 0.01
+recovery = 0.005
+
+net = pnet.PNet()
+
+net.add_places('susceptible', {'susceptible': 100})
+net.add_places('infected', {'infected': 0})
+
+def susceptible_infected(places): 
+    #susceptible = places['susceptible'].attributes['susceptible']
+    susceptible = 1000
+    return infection * susceptible
+    
+def infected_susceptible(places):
+    infected = places['infected'].attributes['infected']
+    return recovery * infected
+    
+net.add_rules('infection', 'function', 
+              ['susceptible.susceptible -> infected.infected', 
+               susceptible_infected, 
+               'susceptible.susceptible > 0'])
+net.add_rules('recovery', 'function', 
+              ['infected.infected -> susceptible.susceptible', 
+               infected_susceptible, 
+               'infected.infected > 0'])
+
+net.simulate(500, 1, 1)
+
+from pprint import pprint
+pprint(net.rules)
+
+data = net.report_tokens()
+headers = ['timestep'] + data[0][1]
+
+f = open('sis.csv', 'w')
+f.write(','.join(headers) + '\n')
+for tdata in data:
+    tdata = [tdata[0]] + [str(x) for x in tdata[2]]
+    f.write(','.join(tdata) + '\n')
+f.close()


Test patch:
diff --git a/test/t_hypothesis.py b/test/t_hypothesis.py
new file mode 100644
index 0000000..8e1d5b5
--- /dev/null
+++ b/test/t_hypothesis.py
@@ -0,0 +1,290 @@
+import sys
+import os
+import unittest
+
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+import hypothesis as N
+    
+class testNormalDistribution(unittest.TestCase):
+    
+    def testZ1Mean1Variance(self): 
+        """Test 1: Z-test for a population mean (variance known)"""
+        self.assertAlmostEqual(N.Z1Mean1Variance(pmean = 4.0, smean = 4.6, 
+            pvar = 1.0, ssize = 9, confidence = 0.975)[2], 1.8)
+        self.assertFalse(N.Z1Mean1Variance(pmean = 4.0, smean = 4.6, 
+            pvar = 1.0, ssize = 9, confidence = 0.975)[4])
+            
+    def testZ2Mean1Variance(self): 
+        """Test 2: Z-test for two population means (variances known and equal)
+        """
+        self.assertAlmostEqual(N.Z2Mean1Variance(smean1 = 1.2, smean2 = 1.7,
+            pvar = 1.4405, ssize1 = 9, ssize2 = 16, confidence = 0.975)[2], 
+            -0.83304408)
+        self.assertFalse(N.Z2Mean1Variance(smean1 = 1.2, smean2 = 1.7,
+            pvar = 1.4405, ssize1 = 9, ssize2 = 16, confidence = 0.975)[4])
+            
+    def testZ2Mean2Variance(self): 
+        """Test 3: Z-test for two population means (variances known and 
+        unequal)"""
+        self.assertAlmostEqual(N.Z2Mean2Variance(smean1 = 80.02, 
+            smean2 = 79.98, pvar1 = 0.000576, pvar2 = 0.001089,
+            ssize1 = 13, ssize2 = 8, confidence = 0.975)[2], 
+            2.977847)
+        self.assertTrue(N.Z2Mean2Variance(smean1 = 80.02, 
+            smean2 = 79.98, pvar1 = 0.000576, pvar2 = 0.001089,
+            ssize1 = 13, ssize2 = 8, confidence = 0.975)[4])
+            
+    def testZ1Proportion(self): 
+        """Test 4: Z-test for a proportion (binomial distribution)"""
+        self.assertAlmostEqual(N.Z1Proportion(spro = 0.5, ppro = 0.4, 
+            ssize = 100, confidence = 0.975)[2], 2.23606798)
+        self.assertTrue(N.Z1Proportion(spro = 0.5, ppro = 0.4, 
+            ssize = 100, confidence = 0.975)[4])
+            
+    def testZ2Proportion(self): 
+        """Test 5: Z-test for the equality of two proportions (binomial 
+        distribution)"""
+        self.assertAlmostEqual(N.Z2Proportion(spro1 = 0.00325, spro2 = 0.0573, 
+            ssize1 = 952, ssize2 = 1168, confidence = 0.025)[2], -6.9265418)
+        self.assertFalse(N.Z2Proportion(spro1 = 0.00325, spro2 = 0.0573, 
+            ssize1 = 952, ssize2 = 1168, confidence = 0.025)[4])
+            
+    def testZ2Count(self): 
+        """Test 6: Z-test for comparing two counts (Poisson distribution)"""
+        self.assertAlmostEqual(N.Z2Count(time1 = 22, time2 = 30, count1 = 952,
+            count2 = 1168, confidence = 0.975)[2], 2.401630072)
+        self.assertTrue(N.Z2Count(time1 = 22, time2 = 30, count1 = 952,
+            count2 = 1168, confidence = 0.975)[4])
+            
+    def testZPearsonCorrelation(self): 
+        """Test 13: Z-test of a correlation coefficient"""
+        self.assertAlmostEqual(N.ZPearsonCorrelation(sr = 0.75, pr = 0.5, 
+            ssize = 24, confidence = 0.95)[2], 1.94140329)
+        self.assertTrue(N.ZPearsonCorrelation(sr = 0.75, pr = 0.5, 
+            ssize = 24, confidence = 0.95)[4])
+            
+    def testZ2PearsonCorrelation(self): 
+        """Test 14: Z-test for two correlation coefficients"""
+        self.assertAlmostEqual(N.Z2PearsonCorrelation(r1 = 0.5, r2 = 0.3, 
+            ssize1 = 28, ssize2 = 35, confidence = 0.975)[2], 0.89832268)
+        self.assertFalse(N.Z2PearsonCorrelation(r1 = 0.5, r2 = 0.3, 
+            ssize1 = 28, ssize2 = 35, confidence = 0.975)[4])
+            
+    def testZCorrProportion(self): 
+        """Test 23: Z-test for correlated proportions"""
+        self.assertAlmostEqual(N.ZCorrProportion(ny = 15, yn = 9, 
+            ssize = 105, confidence = 0.975)[2], 1.22769962)
+        self.assertFalse(N.ZCorrProportion(ny = 15, yn = 9, 
+            ssize = 105, confidence = 0.975)[4])
+            
+    def testSpearmanCorrelation(self): 
+        """Test 58: Spearman rank correlation test (paired observations)"""
+        self.assertAlmostEqual(N.SpearmanCorrelation(R = 24, ssize = 11, 
+            confidence = 0.975)[2], -2.8173019)
+        self.assertFalse(N.SpearmanCorrelation(R = 24, ssize = 11, 
+            confidence = 0.975)[4])
+            
+
+class testTDistribution(unittest.TestCase):
+    
+    def testt1Mean(self): 
+        """Test 7: t-test for a population mean (population variance unknown)"""
+        self.assertAlmostEqual(N.t1Mean(smean = 3.1, pmean = 4.0, svar = 1.0,
+            ssize = 9, confidence = 0.975)[2], -2.69999999)
+        self.assertFalse(N.t1Mean(smean = 3.1, pmean = 4.0, svar = 1.0,
+            ssize = 9, confidence = 0.975)[4])
+            
+    def testt2Mean2EqualVariance(self):
+        """Test 8: t-test for two population means (population variance unknown 
+        but equal)"""
+        self.assertAlmostEqual(N.t2Mean2EqualVariance(smean1 = 31.75, 
+            smean2 = 28.67, svar1 = 112.25, svar2 = 66.64, ssize1 = 12, 
+            ssize2 = 12, confidence = 0.975)[2], 0.798, places=3)
+        self.assertFalse(N.t2Mean2EqualVariance(smean1 = 31.75, 
+            smean2 = 28.67, svar1 = 112.25, svar2 = 66.64, ssize1 = 12,
+            ssize2 = 12, confidence = 0.975)[4])
+    
+    def testt2Mean2UnequalVariance(self):
+        """Test 9: t-test for two population means (population variance unknown 
+        and unequal)"""
+        self.assertAlmostEqual(N.t2Mean2UnequalVariance(smean1 = 3166.0, 
+            smean2 = 2240.4, svar1 = 6328.67, svar2 = 221661.3, ssize1 = 4, 
+            ssize2 = 9, confidence = 0.975)[2], 5.717, places=2)
+        self.assertTrue(N.t2Mean2UnequalVariance(smean1 = 3166.0, 
+            smean2 = 2240.4, svar1 = 6328.67, svar2 = 221661.3, ssize1 = 4,
+            ssize2 = 9, confidence = 0.975)[4])
+            
+    def testtPaired(self):
+        """Test 10: t-test for two population means (method of paired 
+        comparisons)"""
+        self.assertAlmostEqual(N.tPaired(smean1 = 0.9, smean2 = 1.0, svar = 2.9,
+            ssize = 10, confidence=0.975)[2], -0.11, places=2)
+        self.assertFalse(N.tPaired(smean1 = 0.9, smean2 = 1.0, svar = 2.9,
+            ssize = 10, confidence = 0.975)[4])
+            
+    def testtRegressionCoefficient(self):
+        """Test 11: t-test of a regression coefficient"""
+        self.assertAlmostEqual(N.tRegressionCoefficient(variancex = 15.61,
+            varianceyx = 92.4, b = 5.029, ssize = 12, confidence = 0.975)[2], 
+            0.6232, places=3)
+        self.assertFalse(N.tRegressionCoefficient(variancex = 15.61,
+            varianceyx = 92.4, b = 5.029, ssize = 12, confidence = 0.975)[4])
+    
+    def testtPearsonCorrelation(self):
+        """Test 12: t-test of a correlation coefficient"""
+        self.assertAlmostEqual(N.tPearsonCorrelation(r = 0.32, ssize = 18,
+            confidence = 0.95)[2], 1.35, places=2)
+        self.assertFalse(N.tPearsonCorrelation(r = 0.32, ssize = 18,
+            confidence = 0.95)[4])
+    
+    def testZPearsonCorrelation(self):
+        """Test 13: Z-test of a correlation coefficient"""
+        self.assertAlmostEqual(N.ZPearsonCorrelation(sr = 0.75, pr = 0.50, 
+            ssize = 24, confidence = 0.90)[2], 1.94, places = 2)
+        self.assertTrue(N.ZPearsonCorrelation(sr = 0.75, pr = 0.50,
+            ssize = 24, confidence = 0.90)[4])
+            
+    def testZ2PearsonCorrelation(self):
+        """Test 14: Z-test for two correlation coefficients"""
+        self.assertAlmostEqual(N.Z2PearsonCorrelation(r1 = 0.50, r2 = 0.30,
+        ssize1 = 28, ssize2 = 35, confidence =0.95)[2], 0.8985, places = 3)
+        self.assertFalse(N.Z2PearsonCorrelation(r1 = 0.50, r2 = 0.30,
+        ssize1 = 28, ssize2 = 35, confidence =0.95)[4])
+        
+    def testChiSquarePopVar(self):
+        """Test 15: Chi-square test for a population variance"""
+        self.assertAlmostEqual(N.ChiSquarePopVar(values = (70, 71, 71, 69, 69, 
+            72, 72, 68, 68, 67, 67, 73, 73, 74, 74, 66, 66, 65, 65,
+            75, 75, 76, 76, 64, 64), ssize = 25, pv = 9, 
+            confidence = 0.95)[2], 40.4, places = 1)
+        self.assertTrue(N.ChiSquarePopVar(values = (70, 71, 71, 69, 69, 
+            72, 72, 68, 68, 67, 67, 73, 73, 74, 74, 66, 66, 65, 65, 75, 75, 76,
+            76, 64, 64), ssize = 25, pv = 9, confidence = 0.95)[4])
+    
+    def testFVarianceRatio(self):
+        """Test 16: F-test for two population variances (variance ratio test)"""
+        self.assertAlmostEqual(N.FVarianceRatio(var1 = 0.36, var2 = 0.087, 
+            ssize1 = 6, ssize2 = 4, confidence = 0.95)[2], 4.14, places=2)
+        self.assertFalse(N.FVarianceRatio(var1 = 0.36, var2 = 0.087,
+            ssize1 = 6, ssize2 = 4, confidence = 0.95)[4])
+
+    def testF2CorrelatedObs(self):
+        """Test 17: F-test for two population variances 
+        with correlated observations)"""
+        self.assertAlmostEqual(N.F2CorrelatedObs(var1 = 0.36,
+            var2 = 0.087, ssize1 = 6, ssize2 = 6, r = 0.811, 
+            confidence = 0.95)[2], 0.796, places = 2)
+        self.assertFalse(N.F2CorrelatedObs(var1 = 0.36, var2 = 0.087,
+            ssize1 = 6, ssize2 = 6, r = 0.811, confidence = 0.95)[4])
+            
+    # def testFishercumulant(self):
+        # """Test 20: Fisher's cumulant test for normality of a population"""
+        # self.assertAlmostEqual(N.Fishercumulant(m1 = 151, m2 = 805, m3 = 1837, 
+            # m4 = 10753, ssize = 190, confidence = 0.95)[2], 0.449, places = 2)
+        # self.assertFalse(N.Fishercumulant(m1 = 151, m2 = 805, m3 = 1837, 
+            # m4 = 10753, ssize = 190, confidence = 0.95)[4])
+            
+    # def testDixonTest(self):
+        # """Test 21: Dixon's test for outliers"""
+        # self.assertAlmostEqual(N.DixonTest(values = (326, 177, 176, 157), 
+            # n = 4, confidence = 0.95)[2], 0.882, places = 2)
+        # self.assertFalse(N.DixonTest(values = (326, 177, 176, 157), 
+            # n = 4, confidence = 0.95)[4])
+            
+    # def testFTestAnalysisofVar(self):
+        # """Test 22: F-test for K population means (analysis of variance)"""
+        # self.assertAlmostEqual(N.FTestAnalysisofVar(s = ((5, 4, 3),
+            # (5, 5, 4, 4, 3, 3), (4, 6, 8, 10)), k = 3,
+            # confidence = 0.95)[2], 0.002433, places = 4)
+        # self.assertTrue(N.FTestAnalysisofVar(s = ((5, 4, 3),
+            # (5, 5, 4, 4, 3, 3), (4, 6, 8, 10)), k = 3, confidence = 0.95)[4])
+            
+    def testZCorrProportion(self):
+        """Test 23: Z-test for correlated proportions"""
+        self.assertAlmostEqual(N.ZCorrProportion(ssize = 105, ny = 15,
+        yn = 9, confidence = 0.95)[2], 1.23, places = 1)
+        self.assertFalse(N.ZCorrProportion(ssize = 105, ny = 15,
+        yn = 9, confidence = 0.95)[4])
+        
+    def testChisq2Variance(self):
+        """Test 24: Chi-square test for an assumed population variance"""
+        self.assertAlmostEqual(N.Chisq2Variance(ssize = 25, svar = 12, 
+        pvar = 9, confidence = 0.95)[2], 32)
+        self.assertFalse(N.Chisq2Variance(ssize = 25, svar = 12, 
+        pvar = 9, confidence = 0.95)[4])
+        
+    def testF2Count(self):
+        """Test 25: F-test for two counts (Poisson distribution)"""
+        self.assertAlmostEqual(N.F2Count(count1 = 13, count2 = 3,
+            confidence = 0.95)[2], 3.25)
+        self.assertTrue(N.F2Count(count1= 13, count2 = 3, 
+            confidence = 0.95)[4])
+            
+    def testChisqFit(self):
+        """Test 37: Chi-square test for goodness of fit"""
+        self.assertAlmostEqual(N.ChisqFit(observed = (25, 17, 15, 23, 24, 16), 
+        expected = (20, 20, 20, 20, 20, 20), confidence = 0.95)[2], 5.0)
+        self.assertFalse(N.ChisqFit(observed = (25, 17, 15, 23, 24, 16), 
+        expected = (20, 20, 20, 20, 20, 20), confidence = 0.95)[4])
+            
+    def testtx2testofKcounts(self):
+        """Test 38: The x2-test for compatibility of K counts"""
+        self.assertAlmostEqual(N.tx2testofKcounts(T = (1, 1, 1, 1),
+            V = (5, 12, 18, 19), confidence = 0.95)[2], 9.259, places = 2)
+        self.assertTrue(N.tx2testofKcounts(T = (1, 1, 1, 1),
+            V = (5, 12, 18, 19), confidence = 0.95)[4])
+    
+    def testChisq2x2(self):
+        """Test 40: Chi-square test for consistency in 2x2 table"""
+        self.assertAlmostEqual(N.Chisq2x2(s1 = (15, 85), s2 = (4, 77),
+            ssize = 180, confidence = 0.95)[2], 4.79, places = 1)
+        self.assertTrue(N.Chisq2x2(s1 = (15, 85), s2 = (4, 77), ssize = 180, 
+            confidence = 0.95),[4])
+            
+    def testChisquareKx2table(self):
+        """Test 41: The x2-test for consistency in a K x 2 table"""
+        self.assertAlmostEqual(N.ChisquareKx2table(c1 = (3, 4, 5),
+            c2 = (7, 2, 4), k = 3, confidence = 0.95)[2], 2.344, places = 2)
+        self.assertFalse(N.ChisquareKx2table(c1 = (3, 4, 5),
+            c2 = (7, 2, 4), k = 3, confidence = 0.95)[4])
+            
+    def testChisquare2xKtable(self):
+        """Test 43: The x2-test for consistency in a 2 x K table"""
+        self.assertAlmostEqual(N.Chisquare2xKtable(s1 = (50, 47, 56),
+            s2 = (5, 14, 8), k = 3, confidence = 0.95)[2], 4.84, places = 2)
+        self.assertFalse(N.Chisquare2xKtable(s1 = (50, 47, 56),
+            s2 = (5, 14, 8), k = 3, confidence = 0.95)[4])
+            
+    # def testChisquarePxQ(self):
+        # """Test 44: The x2-test for independence in a p x q table"""
+        # self.assertAlmostEqual(N.ChisquarePxQ(d = ((32, 13), (14, 22), (6, 9)),
+            # confidence = 0.95)[2], 10.67, places = 2)
+        # self.assertTrue(N.ChisquarePxQ(d = ((32, 13), (14, 22), (6, 9)),
+            # confidence = 0.95)[4])
+    
+    # def testt2MediansPairedObs(self):
+        # """Test 46: The sign test for two medians (paired observations)"""
+        # self.assertAlmostEqual(N.t2MediansPairedObs(x = (0.19, 0.22, 0.18, 0.17, 
+            # 1.20, 0.14, 0.09, 0.13, 0.26, 0.66), y = (0.21, 0.27, 0.15, 
+            # 0.18, 0.40, 0.08, 0.14, 0.28, 0.30, 0.68), confidence = 0.90)[2], 3)
+        # self.assertFalse(N.t2MediansPairedObs(x = (0.19, 0.22, 0.18, 0.17, 
+            # 1.20, 0.14, 0.09, 0.13, 0.26, 0.66), y = (0.21, 0.27, 0.15, 
+            # 0.18, 0.40, 0.08, 0.14, 0.28, 0.30, 0.68), confidence = 0.90)[4])
+    
+    def testMedianTestfor2Pop(self):
+        """Test 50: The median test of two populations"""
+        self.assertAlmostEqual(N.MedianTestfor2Pop(s1 = (9, 6), 
+            s2 = (6, 9), confidence = 0.95)[2], 0.53, places = 1)
+        self.assertFalse(N.MedianTestfor2Pop(s1 = (9, 6), s2 = (6, 9),
+            confidence = 0.95)[4])
+    
+    def testZtestLogOddsRatio(self):
+        """Test 84: Z-test for comparing sequential contingencies acoss two 
+        groups using the 'log odds ratio' """
+        self.assertAlmostEqual(N.ZtestLogOddsRatio(group1 = (76, 79, 100, 200), 
+            group2 = (80, 43, 63, 39), confidence = 0.95)[2], 1.493, places=3)
+        self.assertFalse(N.ZtestLogOddsRatio(group1 = (76, 79, 100, 200), 
+            group2 = (80, 43, 63, 39), confidence = 0.95)[4])
+            
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/test/t_lindenmayer.py b/test/t_lindenmayer.py
new file mode 100644
index 0000000..15c57c3
--- /dev/null
+++ b/test/t_lindenmayer.py
@@ -0,0 +1,143 @@
+import sys
+import os
+import unittest
+import re
+
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+import lindenmayer as N
+
+class testReplacement(unittest.TestCase):
+    '''
+    Test for single replacement rule without priority.
+    Command length = 1
+    '''
+    def setUp(self):
+        s = N.lindenmayer(1)
+        r = [['A', 'BAC']]
+        s.add_rules(r)
+        axiom = 'A'
+        self.result = []
+        for i in range(10):
+            axiom = s._apply_rules(axiom)
+            self.result.append(axiom)
+        self.answer = ['BAC',
+                       'BBACC',
+                       'BBBACCC',
+                       'BBBBACCCC',
+                       'BBBBBACCCCC',
+                       'BBBBBBACCCCCC',
+                       'BBBBBBBACCCCCCC',
+                       'BBBBBBBBACCCCCCCC',
+                       'BBBBBBBBBACCCCCCCCC',
+                       'BBBBBBBBBBACCCCCCCCCC']
+    def testGeneration(self):
+        self.assertEqual(self.result, self.answer)
+
+class testReplacementPriority(unittest.TestCase):
+    '''
+    Test for replacement rules with priority.
+    Command length = 1
+    '''
+    def setUp(self):
+        s = N.lindenmayer(1)
+        r = [['A', 'BAC', 1],
+             ['B', 'BC', 2]]
+        s.add_rules(r)
+        axiom = 'A'
+        self.result = []
+        for i in range(8):
+            axiom = s._apply_rules(axiom)
+            self.result.append(axiom)
+        self.answer = ['BCAC',
+                       'BCCBCACC',
+                       'BCCCBCCBCACCC',
+                       'BCCCCBCCCBCCBCACCCC',
+                       'BCCCCCBCCCCBCCCBCCBCACCCCC',
+                       'BCCCCCCBCCCCCBCCCCBCCCBCCBCACCCCCC',
+                       'BCCCCCCCBCCCCCCBCCCCCBCCCCBCCCBCCBCACCCCCCC',
+                       'BCCCCCCCCBCCCCCCCBCCCCCCBCCCCCBCCCCBCCCBCCBCACCCCCCCC']
+    def testGeneration(self):
+        self.assertEqual(self.result, self.answer)
+
+class testReplacementNoPriority(unittest.TestCase):
+    '''
+    Test for replacement rules without priority.
+    Command length = 1
+    '''
+    def setUp(self):
+        s = N.lindenmayer(1)
+        r = [['A', 'BAC'],
+             ['B', 'BC']]
+        s.add_rules(r)
+        axiom = 'A'
+        self.result = []
+        for i in range(8):
+            axiom = s._apply_rules(axiom)
+            self.result.append(axiom)
+        self.answer = ['BAC',
+                       'BCBACC',
+                       'BCCBCBACCC',
+                       'BCCCBCCBCBACCCC',
+                       'BCCCCBCCCBCCBCBACCCCC',
+                       'BCCCCCBCCCCBCCCBCCBCBACCCCCC',
+                       'BCCCCCCBCCCCCBCCCCBCCCBCCBCBACCCCCCC',
+                       'BCCCCCCCBCCCCCCBCCCCCBCCCCBCCCBCCBCBACCCCCCCC']
+    def testGeneration(self):
+        self.assertEqual(self.result, self.answer)
+
+class testReplacement2(unittest.TestCase):
+    '''
+    Test for replacement rules without priority.
+    Command length = 2
+    '''
+    def setUp(self):
+        s = N.lindenmayer(2)
+        r = [['AA', 'BBAAC'],
+             ['AB', 'ABC'],
+             ['AC', 'BCC']]
+        s.add_rules(r)
+        axiom = 'AA'
+        self.result = []
+        for i in range(8):
+            axiom = s._apply_rules(axiom)
+            self.result.append(axiom)
+        self.answer = ['BBAAC',
+                       'BBBBAACC',
+                       'BBBBBBAACCC',
+                       'BBBBBBBBAACCCC',
+                       'BBBBBBBBBBAACCCCC',
+                       'BBBBBBBBBBBBAACCCCCC',
+                       'BBBBBBBBBBBBBBAACCCCCCC',
+                       'BBBBBBBBBBBBBBBBAACCCCCCCC']
+    def testGeneration(self):
+        self.assertEqual(self.result, self.answer)
+    
+class testFunction(unittest.TestCase):
+    '''
+    Test for function rules.
+    Command length = 1
+    '''
+    def replaceFunction(self, dstring, position):
+        if dstring[position+3] == 'O': return 'BAAB'
+        elif dstring[position-1] == 'O': return 'AABB'
+        else: return 'OOAB'
+    def setUp(self):
+        s = N.lindenmayer(2)
+        r = [['AB', self.replaceFunction, 1, 'function']]
+        s.add_rules(r)
+        axiom = 'ACCCABABDD'
+        self.result = []
+        for i in range(5):
+            axiom = s._apply_rules(axiom)
+            self.result.append(axiom)
+        self.answer = ['ACCCOOABOOABDD',
+                       'ACCCOOBAABOOAABBDD',
+                       'ACCCOOBABAABOOAABBDD',
+                       'ACCCOOBABABAABOOAABBDD',
+                       'ACCCOOBABABABAABOOAABBDD']
+    def testGeneration(self):
+        self.assertEqual(self.result, self.answer)
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/test/t_neural.py b/test/t_neural.py
new file mode 100644
index 0000000..f9395f0
--- /dev/null
+++ b/test/t_neural.py
@@ -0,0 +1,261 @@
+import sys
+import os
+import copy
+import pprint
+import unittest
+
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+import neural as n
+
+brainfile = 'testbrain.db'
+
+class testSimpleBrain(unittest.TestCase):
+    '''
+    setUp(self): add_neuron()
+    setUp(self): add_synapse(x[0], x[1])
+
+    testAddNeuron1(self): get_neurons('alive')
+    testAddNeuron1(self): add_neuron()
+
+    testAddNeuron2(self): add_neuron(name='test', summation='sumtest', transfer='transfer_test', threshold='0.1', pstate='0.11')
+    testAddNeuron2(self): get_neuronID_from_name('test')
+    testAddNeuron2(self): get_name_from_neuronID(nID)
+    testAddNeuron2(self): get_neuron_state(nID, 'summation')
+    testAddNeuron2(self): get_neuron_state(nID, 'pstate')
+    
+    testGetSynapse(self): get_synapses('1', 'weight')
+    testGetSynapse(self): get_synapses('1', 'all')
+    testGetSynapse(self): get_synapses('2', 'weight')
+
+    testAddSynapse1(self): add_synapse('1', '8')
+
+    testDeleteNeuron(self): get_neurons('alive')
+    testDeleteNeuron(self): get_synapses('3', 'weight')
+    testDeleteNeuron(self): get_synapses('6', 'weight')
+    testDeleteNeuron(self): delete_neuron('6')
+    testDeleteNeuron(self): get_neurons('alive')
+    testDeleteNeuron(self): get_synapses('3', 'weight')
+    testDeleteNeuron(self): get_synapses('6', 'weight')
+
+    testDeleteOrphanedNeurons(self): delete_orphaned_neurons()
+
+    testDeleteSynapse(self): get_synapses('1', 'weight')
+    testDeleteSynapse(self): delete_synapse('1', '3')
+    testDeleteSynapse(self): get_synapses('1', 'weight')
+
+    testDeleteSynapseByState(self): add_synapse(x[0], x[1])
+    testDeleteSynapseByState(self): get_all_synaptic_states('weight')
+    testDeleteSynapseByState(self): delete_synapse_by_state(0.1, 'lowest', 'weight')
+    testDeleteSynapseByState(self): get_all_synaptic_states('weight')
+
+    testSetNeuronState(self): set_neuron_state('1', 'trialstate', 0.5)
+    testSetNeuronState(self): get_neuron_state('1', 'trialstate')
+
+    testSetSynapseState(self): set_synapse_state('1', '2', 'testvalue', 'teststate')
+    testSetSynapseState(self): get_synapse_state('1', '2', 'teststate')
+
+    testAddInputChannel(self): get_input_values('1')
+    testAddInputChannel(self): add_input_channel('eye', '1')
+    testAddInputChannel(self): get_input_values('1')
+
+    testSetInput(self): set_input('eye', {'1': 0.9})
+    testSetInput(self): get_input_values('1')
+    testSetInput(self): set_input('eye', {'1': 0.7})
+    testSetInput(self): get_input_values('1')
+    testSetInput(self): set_input('eye', {'1': 0.15})
+    testSetInput(self): get_input_values('1')
+
+    testExecuteNeuron1(self): set_input('eye', {'1': 0.15})
+    testExecuteNeuron1(self): get_input_values('1')
+    testExecuteNeuron1(self): get_neuron_state('1', 'pstate')
+    testExecuteNeuron1(self): get_input_values('2')
+    testExecuteNeuron1(self): get_input_values('3')
+    testExecuteNeuron1(self): execute_neuron('1', 'pstate', 'weight')
+    testExecuteNeuron1(self): get_neuron_state('1', 'pstate')
+    testExecuteNeuron1(self): get_input_values('2')
+    testExecuteNeuron1(self): get_input_values('3')
+
+    testExecuteNeuron2(self): set_input('eye', {'1': 0.15})
+    testExecuteNeuron2(self): get_input_values('1')
+    testExecuteNeuron2(self): get_neuron_state(str(ID), state='pstate') 
+    testExecuteNeuron2(self): execute_neuron(str(ID), 'pstate', 'weight')
+    testExecuteNeuron2(self): get_neuron_state(str(ID), state='pstate')
+    '''
+    def setUp(self):
+        self.brain = n.Brain(brainfile)
+        self.brain.format()
+        for i in range(10):
+            self.brain.add_neuron()
+        for x in [('1', '2'), ('1', '3'), ('2', '4'), ('4', '5'), ('3', '6'),
+                  ('5', '7'), ('6', '7')]:
+            self.brain.add_synapse(x[0], x[1])
+
+    def testAddNeuron1(self):
+        nIDs = self.brain.get_neurons('alive')
+        self.assertEqual(len(nIDs), 10)
+        for i in range(5):
+            self.brain.add_neuron()
+        nIDs = self.brain.get_neurons('alive')
+        self.assertEqual(len(nIDs), 15)
+
+    def testAddNeuron2(self):
+        nID = self.brain.add_neuron(name='test', summation='sumtest',
+                                    transfer='transfer_test', threshold='0.1',
+                                    pstate='0.11')
+        ID = self.brain.get_neuronID_from_name('test')
+        self.assertEqual(nID, ID)
+        name = self.brain.get_name_from_neuronID(nID)
+        self.assertEqual(name, 'test')
+        summation = self.brain.get_neuron_state(nID, 'summation')
+        self.assertEqual(summation, 'sumtest')
+        pstate = self.brain.get_neuron_state(nID, 'pstate')
+        self.assertEqual(pstate, '0.11')
+
+    def testGetSynapse(self):
+        nIDs = self.brain.get_synapses('1', 'weight')
+        self.assertEqual(nIDs, ['2', '3'])
+        nIDs = self.brain.get_synapses('1', 'all')
+        self.assertEqual(nIDs, ['2', '3'])
+        nIDs = self.brain.get_synapses('2', 'weight')
+        self.assertEqual(nIDs, ['4'])
+
+    def testAddSynapse1(self):
+        self.brain.add_synapse('1', '8')
+        nIDs = self.brain.get_synapses('1', 'weight')
+        self.assertEqual(nIDs, ['2', '3', '8'])
+
+    def testDeleteNeuron(self):
+        # before deleting neuron #6 - 10 neurons present
+        nIDs = self.brain.get_neurons('alive')
+        self.assertEqual(len(nIDs), 10)
+        # before deleting neuron #6 - (#3 --> #6)
+        nIDs = self.brain.get_synapses('3', 'weight')
+        self.assertEqual(nIDs, ['6'])
+        # before deleting neuron #6 - (#6 --> #7)
+        nIDs = self.brain.get_synapses('6', 'weight')
+        self.assertEqual(nIDs, ['7'])
+        # delete neuron #6
+        self.brain.delete_neuron('6')
+        # after deleting neuron #6 - 9 neurons present
+        nIDs = self.brain.get_neurons('alive')
+        self.assertEqual(len(nIDs), 9)
+        # after deleting neuron #6 - (#3 --> nothing)
+        nIDs = self.brain.get_synapses('3', 'weight')
+        self.assertEqual(nIDs, [])
+        # after deleting neuron #6 - (nothing --> #7)
+        nIDs = self.brain.get_synapses('6', 'weight')
+        self.assertEqual(nIDs, [])
+
+    def testDeleteOrphanedNeurons(self):
+        self.brain.delete_orphaned_neurons()
+        nIDs = self.brain.get_neurons('alive')
+        self.assertEqual(len(nIDs), 7)
+        self.assertEqual(nIDs, ['1', '2', '3', '4', '5', '6', '7'])
+
+    def testDeleteSynapse(self):
+        nIDs = self.brain.get_synapses('1', 'weight')
+        self.assertEqual(nIDs, ['2', '3'])
+        self.brain.delete_synapse('1', '3')
+        nIDs = self.brain.get_synapses('1', 'weight')
+        self.assertEqual(nIDs, ['2'])
+
+    def testDeleteSynapseByState(self):
+        # add 14 more synapses (only 7 in setUp) to 21
+        for x in [('6', '8'), ('6', '9'), ('6', '10'), 
+                  ('5', '8'), ('5', '9'), ('5', '10'), 
+                  ('7', '10'), ('8', '10'), ('9', '10'),
+                  ('3', '8'), ('3', '9'), ('3', '10'), 
+                  ('4', '8'), ('4', '9')]:
+            self.brain.add_synapse(x[0], x[1])
+        # remove lowest 10% of synapses - 2 synapses removed (19 remaining)
+        old_synaptic_states = self.brain.get_all_synaptic_states('weight')
+        self.brain.delete_synapse_by_state(0.1, 'lowest', 'weight')
+        new_synaptic_states = self.brain.get_all_synaptic_states('weight')
+        self.assertEqual(len(new_synaptic_states), 19)
+
+    def testSetNeuronState(self):
+        self.brain.set_neuron_state('1', 'trialstate', 0.5)
+        pstate = self.brain.get_neuron_state('1', 'trialstate')
+        self.assertEqual(pstate, '0.5')
+
+    def testSetSynapseState(self):
+        self.brain.set_synapse_state('1', '2', 'testvalue', 'teststate')
+        value = self.brain.get_synapse_state('1', '2', 'teststate')
+        self.assertEqual(value, 'testvalue')
+
+    def testAddInputChannel(self):
+        # before adding input channel - there is no input into neuron #1
+        input_vector = self.brain.get_input_values('1')
+        self.assertEqual(len(input_vector), 0)
+        # adding input channel #eye
+        self.brain.add_input_channel('eye', '1')
+        # after adding input channel - there is 1 input into neuron #1
+        input_vector = self.brain.get_input_values('1')
+        self.assertEqual(len(input_vector), 1)
+
+    def testSetInput(self):
+        # test 1
+        self.brain.set_input('eye', {'1': 0.9})
+        input_vector = self.brain.get_input_values('1')
+        self.assertEqual(input_vector, [0.9])
+        # test 2
+        self.brain.set_input('eye', {'1': 0.7})
+        input_vector = self.brain.get_input_values('1')
+        self.assertEqual(input_vector, [0.7])
+        # test 3
+        self.brain.set_input('eye', {'1': 0.15})
+        input_vector = self.brain.get_input_values('1')
+        self.assertEqual(input_vector, [0.15])
+
+    def testExecuteNeuron1(self):
+        # set an input into neuron #1
+        self.brain.set_input('eye', {'1': 0.15})
+        input_vector = self.brain.get_input_values('1')
+        self.assertEqual(input_vector, [0.15])
+        # get data before execution of neuron #1
+        old_pstate = self.brain.get_neuron_state('1', 'pstate')
+        old_input2 = self.brain.get_input_values('2')
+        old_input3 = self.brain.get_input_values('3')
+        # execute neuron #1
+        self.brain.execute_neuron('1', 'pstate', 'weight')
+        # after execution, neuron #1's pstate will be updated
+        new_pstate = self.brain.get_neuron_state('1', 'pstate')
+        self.assertNotEqual(old_pstate, new_pstate)
+        # after execution, neuron #2's input vector will be updated
+        new_input2 = self.brain.get_input_values('2')
+        self.assertEqual(len(old_input2), 0)
+        self.assertEqual(len(new_input2), 1)
+        self.assertNotEqual(old_input2, new_input2)
+        #print
+        #print 'Neuron #2 Input (before): ', old_input2
+        #print 'Neuron #2 Input (after): ', new_input2
+        # after execution, neuron #2's input vector will be updated
+        new_input3 = self.brain.get_input_values('3')
+        self.assertEqual(len(old_input2), 0)
+        self.assertEqual(len(new_input2), 1)
+        self.assertNotEqual(old_input3, new_input3)
+        #print 'Neuron #3 Input (before): ', old_input3
+        #print 'Neuron #3 Input (after): ', new_input3
+
+    def testExecuteNeuron2(self):
+        # set an input into neuron #1
+        self.brain.set_input('eye', {'1': 0.15})
+        input_vector = self.brain.get_input_values('1')
+        self.assertEqual(input_vector, [0.15])
+        # get data before execution of neurons
+        old_pstates = [self.brain.get_neuron_state(str(ID), state='pstate') 
+                       for ID in range(1, 11)]
+        # execute neurons #1 to #10
+        for ID in self.brain.sequence:
+            self.brain.execute_neuron(str(ID), 'pstate', 'weight')
+        # get data after execution of neurons
+        new_pstates = [self.brain.get_neuron_state(str(ID), state='pstate') 
+                       for ID in range(1, 11)]
+        self.assertNotEqual(old_pstates, new_pstates)
+        print("Execute Neuron 2")
+        print('pstates (before): ' + str(old_pstates))
+        print('pstates (after): ' + str(new_pstates)) 
+        
+  
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/test/t_nrpy.py b/test/t_nrpy.py
new file mode 100644
index 0000000..25c0326
--- /dev/null
+++ b/test/t_nrpy.py
@@ -0,0 +1,325 @@
+import sys
+import os
+import unittest
+
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+import nrpy as N
+    
+
+class testNR(unittest.TestCase):
+#    def testadi(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testairy(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testamebsa(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testamoeba(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testanneal(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testanorm2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testarcmak(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testarcode(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testarcsum(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testavevar(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbadluk(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbalanc(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbanbks(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbandec(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbanmul(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbcucof(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbcuint(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbeschb(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbessi(self): self.assertAlmostEqual(N.bessi(2.0, 5.0), 17.505615)
+    def testbessi0(self): self.assertAlmostEqual(N.bessi0(0.2), 1.0100250)
+    def testbessi1(self): self.assertAlmostEqual(N.bessi1(0.2), 0.10050083)
+#    def testbessik(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbessj(self): self.assertAlmostEqual(N.bessj(2, 1.0), 0.1149034849)
+    def testbessj0(self): self.assertAlmostEqual(N.bessj0(0.0), 1.0)
+    def testbessj1(self): self.assertAlmostEqual(N.bessj1(2.0), 0.5767248)
+#    def testbessjy(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbessk(self): self.assertAlmostEqual(N.bessk(0.2), 49.512430)
+#    def testbessk0(self): self.assertAlmostEqual(N.bessk(0.1), 2.427069)
+#    def testbessk1(self): self.assertAlmostEqual(N.bessk1(0,1), 9.8538451)
+    def testbessy(self): self.assertAlmostEqual(N.bessy(2, 1.0), -1.650682607)
+    def testbessy0(self): self.assertAlmostEqual(N.bessy0(1.0), 0.0882570)
+    def testbessy1(self): self.assertAlmostEqual(N.bessy1(15.0), 0.0210736)
+    def testbeta(self): self.assertAlmostEqual(N.beta(0.2, 1.0), 5.000000)
+#    def testbetacf(self): self.assertAlmostEqual(N.<something>( ), testdata)
+    def testbetai(self): self.assertAlmostEqual(N.betai(10.0, 5.0, 1.0), 1.000000)
+    def testbico(self): self.assertAlmostEqual(N.bico(6, 3), 20)
+#    def testbnldev(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbrent(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbroydn(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testbsstep(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcaldat(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcdf_binomial(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcdf_poisson(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcel(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testchder(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testchebev(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testchebpc(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testchebtf(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testchint(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcholdc(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcholsl(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testchsone(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testchstwo(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcisi(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcntab1(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcntab2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testconvlv(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcorrel(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcosft(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcosft1(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcosft2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcovsrt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcrank(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testcyclic(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testdaub4(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testdawson(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testdbrent(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testddpoly(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testdecchk(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testdes(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testdf1dim(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testdfpmin(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testdfridr(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testdftint(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testeclass(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testeclazz(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testei(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testeigsrt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testel2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testelle(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testellf(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testellpi(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testelmhes(self): self.assertAlmostEqual(N.<something>( ), testdata)
+    def testerf(self): self.assertAlmostEqual(N.erf(0.0), 0.0)
+#    def testerfc(self): self.assertAlmostEqual(N.<something>( ), testdata)
+    def testerfcc(self): self.assertAlmostEqual(N.erfcc(0.5), 0.479500092)
+#    def testeulsum(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testevlmem(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testexpdev(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testexpint(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testf1dim(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfactln(self): self.assertAlmostEqual(N.f( ), testdata)
+#    def testfactrl(self): self.assertAlmostEqual(N.factrl(5), 120)
+#    def testfasper(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfgauss(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfit(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfitexy(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfixrts(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfleg(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testflmoon(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfour1(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfourfs(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfourn(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfpoly(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfred2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfredex(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfredin(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfrenel(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testfrprmn(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testftest(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testgamdev(self): self.assertAlmostEqual(N.<something>( ), testdata)
+    def testgammln(self): self.assertAlmostEqual(N.gammln(0.5), 0.5723649)
+    def testgammp(self): self.assertAlmostEqual(N.gammp(5.0, 5.0), 0.55950669)
+    def testgammq(self): self.assertAlmostEqual(N.gammq(5.0, 5.0), 0.440493303)
+#    def testgasdev(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testgaucof(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testgauher(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testgaujac(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testgaulag(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testgauleg(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testgaussj(self): self.assertAlmostEqual(N.<something>( ), testdata)
+    def testgcf_0(self): self.assertAlmostEqual(N.gcf(5.0, 7.0)[0], 0.1729916)
+    def testgcf_1(self): self.assertAlmostEqual(N.gcf(5.0, 7.0)[1], 3.17805383)
+#    def testgolden(self): self.assertAlmostEqual(N.<something>( ), testdata)
+    def testgser_1(self): self.assertAlmostEqual(N.gser(5.0, 5.0)[1], 3.17805383)
+    def testgser_0(self): self.assertAlmostEqual(N.gser(5.0, 5.0)[0], 0.55950669)
+#    def testhpsel(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhpsort(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhqr(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhufapp(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhufdec(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhufenc(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhufmak(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhunt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhypdrv(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhypgeo(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testhypser(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testicrc(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testicrc1(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testigray(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testindexx(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testirbit1(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testirbit2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testjacobi(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testjulday(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testkendl1(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testkendl2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testks2d1s(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testks2d2s(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testksone(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testkstwo(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testlaguer(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testlfit(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testlinbcg(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testlinmin(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testlnsrch(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testlocate(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testlop(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testlubksb(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testludcmp(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmachar(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmath(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmdian1(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmdian2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmedfit(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmemcof(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmgfas(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmglin(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmidexp(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmidinf(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmidpnt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmidsql(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmidsqu(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmiser(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmmid(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmnbrak(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmnewt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmoment(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmp2dfr(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmpdiv(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmpinv(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmppi(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmprove(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmrqcof(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testmrqmin(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testnewt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testodeint(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testorthog(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpade(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpccheb(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpcshft(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpearsn(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testperiod(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpiksr2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpiksrt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testplgndr(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpoicoe(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpoidev(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpolcof(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpoldiv(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpolin2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpolint(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpowell(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpredic(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testprobks(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpsdes(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpwt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testpwtest(self): self.assertAlmostEqual(N.<something>( ), testdata)
+    def testpythag(self): self.assertAlmostEqual(N.pythag(4.0, 4.0), 5.6568542)
+#    def testpzextr(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqcksrt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqgaus(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqrdcmp(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqromb(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqromo(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqroot(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqrsolv(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqrupdt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqsimp(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testqtrap(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testquad3d(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testquadvl(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testran0(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testran1(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testran2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testran3(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testran4(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrank(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testratint(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testratlsq(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testratval(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrc(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrd(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrealft(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrf(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrj(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrk4(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrkdumb(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrkqc(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrkqs(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrlft3(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrofunc(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrtbis(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrtflsp(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrtnewt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrtsafe(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrtsec(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testrzextr(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsavgol(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testscrsho(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testselect(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testselip(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsfroid(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testshell(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testshoot(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testshootf(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsimplx(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsimpr(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsinft(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsmooft(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsncndn(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsobseq(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsolvde(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsor(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsort(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsort2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsort3(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsparse(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testspctrm(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testspear(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsphbes(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsphfpt(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsphoot(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsplie2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsplin2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testspline(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsplint(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testspread(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsprsax(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsprsin(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsprspm(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsprstm(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsprstp(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsprstx(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def teststifbs(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def teststiff(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def teststoerm(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsvbksb(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsvdcmp(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsvdfit(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testsvdvar(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testtoeplz(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testtptest(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testtqli(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testtrapzd(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testtred2(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testtridag(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testttest(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testtutest(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testtwofft(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testvander(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testvegas(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testvoltra(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testwt1(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testwtn(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testwwghts(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testzbrac(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testzbrak(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testzbrent(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testzrhqr(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testzriddr(self): self.assertAlmostEqual(N.<something>( ), testdata)
+#    def testzroots(self): self.assertAlmostEqual(N.<something>( ), testdata)
+
+        
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/test/t_objectdistance.py b/test/t_objectdistance.py
new file mode 100644
index 0000000..32babf1
--- /dev/null
+++ b/test/t_objectdistance.py
@@ -0,0 +1,453 @@
+import sys
+import os
+import unittest
+
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+import objectdistance as D
+
+# for set comparison
+# 5 elements in both
+# 1 element in test
+# 1 element in original
+o1 = ['C', 'D', 'E', 'F', 'G', 'H']
+t1 = ['B', 'E', 'D', 'F', 'G', 'H']
+
+# for list comparison
+# 4 elements in both
+# 1 element in test
+# 1 element in original
+o2 = [1, 1, 1, 0, 1, 1]
+t2 = [1, 1, 0, 1, 1, 1]
+
+# for list comparison in interval/ratio measures
+o3 = [1.0, 2.0, 3.0, 6.0, 8.0, 9.0]
+t3 = [1.0, 2.0, 4.0, 4.0, 8.0, 9.0]
+
+
+class testSet(unittest.TestCase):
+    def testJaccard(self):
+        'Jaccard for set'
+        distance = D.Jaccard(o1, t1)
+        actual = 0.7143
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testSokal_Michener(self):
+        'Sokal and Michener for set'
+        distance = D.Sokal_Michener(o1, t1)
+        actual = 0.7143
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testMatching(self):
+        'Matching for set'
+        distance = D.Matching(o1, t1)
+        actual = 0.4167
+        self.assertAlmostEqual(distance, actual, places=4)
+       
+    def testDice(self):
+        'Dice for set'
+        distance = D.Dice(o1, t1)
+        actual = 0.8333
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testOchiai(self):
+        'Ochiai for set'
+        distance = D.Ochiai(o1, t1)
+        actual = 0.8333
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testOchiai2(self):
+        'Ochiai 2 for set'
+        distance = D.Ochiai2(o1, t1)
+        actual = 0.0000
+        self.assertAlmostEqual(distance, actual, places=4)
+    
+    def testAnderberg(self):
+        'Anderberg for set'
+        distance = D.Anderberg(o1, t1)
+        actual = 0.55555
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testKulczynski2(self):
+        'Kulczynski 2 for set'
+        distance = D.Kulczynski2(o1, t1)
+        actual = 0.8333
+        self.assertAlmostEqual(distance, actual, places=4)    
+
+    def testKulczynski(self):
+        'Kulczynski for set'
+        distance = D.Kulczynski(o1, t1)
+        actual = 2.5000
+        self.assertAlmostEqual(distance, actual, places=4) 
+        
+    def testForbes(self):
+        'Forbes for set'
+        distance = D.Forbes(o1, t1)
+        actual = 0.9722
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testHamann(self):
+        'Hamann for set'
+        distance = D.Hamann(o1, t1)
+        actual = 0.4286
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testSimpson(self):
+        'Simpson for set'
+        distance = D.Simpson(o1, t1)
+        actual = 0.8333
+        self.assertAlmostEqual(distance, actual, places=4)  
+        
+    def testRussel_Rao(self):
+        'Russel and Rao for set'
+        distance = D.Russel_Rao(o1, t1)
+        actual = 0.7143
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testRoger_Tanimoto(self):
+        'Roger and Tanimoto for set'
+        distance = D.Roger_Tanimoto(o1, t1)
+        actual = 0.55555
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testSokal_Sneath(self):
+        'Sokal and Sneath for set'
+        distance = D.Sokal_Sneath(o1, t1)
+        actual = 0.55555
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testSokal_Sneath2(self):
+        'Sokal and Snealth 2 for set'
+        distance = D.Sokal_Sneath2(o1, t1)
+        actual = 0.8333
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testSokal_Sneath3(self):
+        'Sokal and Snealth 3 for set'
+        distance = D.Sokal_Sneath3(o1, t1)
+        actual = 2.5000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testBuser(self):
+        'Buser for set'
+        distance = D.Buser(o1, t1)
+        actual = 0.7835
+        self.assertAlmostEqual(distance, actual, places=4)  
+        
+    def testFossum(self):
+        'Fossum for set'
+        distance = D.Fossum(o1, t1)
+        actual = 3.9375
+        self.assertAlmostEqual(distance, actual, places=4) 
+        
+    def testYuleQ(self):
+        'Yule Q for set'
+        distance = D.YuleQ(o1, t1)
+        actual = -1.0000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testYuleY(self):
+        'Yule Y for set'
+        distance = D.YuleY(o1, t1)
+        actual = -1.0000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testMcconnaughey(self):
+        'McConnaughey for set'
+        distance = D.Mcconnaughey(o1, t1)
+        actual = 0.6667
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testStiles(self):
+        'Stiles for set'
+        distance = D.Stiles(o1, t1)
+        actual = 0.0847
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testPearson(self):
+        'Pearson for set'
+        distance = D.Pearson(o1, t1)
+        actual = -0.1667
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testDennis(self):
+        'Dennis for set'
+        distance = D.Dennis(o1, t1)
+        actual = -0.0630
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testGower_Legendre(self):
+        'Gower and Legendre for set'
+        distance = D.Gower_Legendre(o1, t1)
+        actual = 0.8333
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testTulloss(self):
+        'Tulloss for set'
+        distance = D.Tulloss(o1, t1)
+        actual = 0.8509
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+        
+class testList(unittest.TestCase):
+
+    def testJaccard(self):
+        'Jaccard for list'
+        distance = D.Jaccard(o2, t2, 0, 'List')
+        actual = 0.6667
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testSokal_Michener(self):
+        'Sokal and Michener for list'
+        distance = D.Sokal_Michener(o2, t2, 0, 'List')
+        actual = 0.6667
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testMatching(self):
+        'Matching for list'
+        distance = D.Matching(o2, t2, 0, 'List')
+        actual = 0.4000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testDice(self):
+        'Dice for list'
+        distance = D.Dice(o2, t2, 0, 'List')
+        actual = 0.6667
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testOchiai(self):
+        'Ochiai for list'
+        distance = D.Ochiai(o2, t2, 0, 'List')
+        actual = 0.8000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testOchiai2(self):
+        'Ochiai 2 for list'
+        distance = D.Ochiai2(o2, t2, 0, 'List')
+        actual = 0.0000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testAnderberg(self):
+        'Anderberg for list'
+        distance = D.Anderberg(o2, t2, 0, 'List')
+        actual = 0.5000
+        self.assertAlmostEqual(distance, actual, places=4)
+     
+    def testKulczynski2(self):
+        'Kulczynski 2 for list'
+        distance = D.Kulczynski2(o2, t2, 0, 'List')
+        actual = 0.8000
+        self.assertAlmostEqual(distance, actual, places=4)
+    
+    def testKulczynski(self):
+        'Kulczynski for list'
+        distance = D.Kulczynski(o2, t2, 0, 'List')
+        actual = 2.0000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testForbes(self):
+        'Forbes for list'
+        distance = D.Forbes(o2, t2, 0, 'List')
+        actual = 0.9600
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testHamann(self):
+        'Hamann for list'
+        distance = D.Hamann(o2, t2, 0, 'List')
+        actual = 0.3333
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testSimpson(self):
+        'Simpson for list'
+        distance = D.Simpson(o2, t2, 0, 'List')
+        actual = 0.8000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testRussel_Rao(self):
+        'Russel and Rao for list'
+        distance = D.Russel_Rao(o2, t2, 0, 'List')
+        actual = 0.6667
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testRoger_Tanimoto(self):
+        'Roger and Tanimoto for list'
+        distance = D.Roger_Tanimoto(o2, t2, 0, 'List')
+        actual = 0.5000
+        self.assertAlmostEqual(distance, actual, places=4)
+    
+    def testSokal_Sneath(self):
+        'Sokal and Sneath for list'
+        distance = D.Sokal_Sneath(o2, t2, 0, 'List')
+        actual = 0.5000
+        self.assertAlmostEqual(distance, actual, places=4)
+    
+    def testSokal_Sneath2(self):
+        'Sokal and Sneath 2 for list'
+        distance = D.Sokal_Sneath2(o2, t2, 0, 'List')
+        actual = 0.8000
+        self.assertAlmostEqual(distance, actual, places=4)  
+
+    def testSokal_Sneath3(self):
+        'Sokal and Sneath 3 for list'
+        distance = D.Sokal_Sneath3(o2, t2, 0, 'List')
+        actual = 2.0000
+        self.assertAlmostEqual(distance, actual, places=4)  
+        
+    def testBuser(self):
+        'Buser for list'
+        distance = D.Buser(o2, t2, 0, 'List')
+        actual = 0.7500
+        self.assertAlmostEqual(distance, actual, places=4)  
+        
+    def testFossum(self):
+        'Fossum for list'
+        distance = D.Fossum(o2, t2, 0, 'List')
+        actual = 2.9400
+        self.assertAlmostEqual(distance, actual, places=4)
+    
+    def testYuleQ(self):
+        'Yule Q for list'
+        distance = D.YuleQ(o2, t2, 0, 'List')
+        actual = -1.000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testYuleY(self):
+        'Yule Y for list'
+        distance = D.YuleY(o2, t2, 0, 'List')
+        actual = -1.000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testMcconnaughey(self):
+        'McConnaughey for list'
+        distance = D.Mcconnaughey(o2, t2, 0, 'List')
+        actual = 0.6000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testStiles(self):
+        'Stiles for list'
+        distance = D.Stiles(o2, t2, 0, 'List')
+        actual = -0.0177
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testPearson(self):
+        'Pearson for list'
+        distance = D.Pearson(o2, t2, 0, 'List')
+        actual = -0.2000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testDennis(self):
+        'Dennis for list'
+        distance = D.Dennis(o2, t2, 0, 'List')
+        actual = -0.0816
+        self.assertAlmostEqual(distance, actual, places=4)
+    
+    def testGower_Legendre(self):
+        'Gower and Legendre for list'
+        distance = D.Gower_Legendre(o2, t2, 0, 'List')
+        actual = 0.8000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testTulloss(self):
+        'Tulloss for list'
+        distance = D.Tulloss(o2, t2, 0, 'List')
+        actual = 0.8211
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testHamming2(self):
+        'Hamming for list (Test 1)'
+        distance = D.Hamming(o2, t2)
+        actual = 2
+        self.assertEqual(distance, actual)
+        
+    def testHamming3(self):
+        'Hamming for list (Test 2)'
+        distance = D.Hamming(o3, t3)
+        actual = 2
+        self.assertEqual(distance, actual)
+    
+    def testEuclidean2(self):
+        'Euclidean for list (Test 1)'
+        distance = D.Euclidean(o2, t2)
+        actual = 1.4142
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testEuclidean3(self):
+        'Euclidean for list (Test 2)'
+        distance = D.Euclidean(o3, t3)
+        actual = 2.2361
+        self.assertAlmostEqual(distance, actual, places=4)
+     
+    def testMinkowski2(self):
+        'Minkowski for list (Test 1)'
+        distance = D.Minkowski(o2, t2)
+        actual = 1.2599
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testMinkowski3(self):
+        'Minkowski for list (Test 2)'
+        distance = D.Minkowski(o3, t3)
+        actual = 2.0801
+        self.assertAlmostEqual(distance, actual, places=4)    
+     
+    def testManhattan2(self):
+        'Manhattan for list (Test 1)'
+        distance = D.Manhattan(o2, t2)
+        actual = 2.0
+        self.assertAlmostEqual(distance, actual, places=4)   
+
+    def testManhattan3(self):
+        'Manhattan for list (Test 2)'
+        distance = D.Manhattan(o3, t3)
+        actual = 3.0
+        self.assertAlmostEqual(distance, actual, places=4) 
+        
+    def testCanberra2(self):
+        'Canberra for list (Test 1)'
+        distance = D.Canberra(o2, t2)
+        actual = 2.0
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testCanberra3(self):
+        'Canberra for list (Test 2)'
+        distance = D.Canberra(o3, t3)
+        actual = 0.3429
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testBray_Curtis2(self):
+        'Bray Curtis for list (Test 1)'
+        distance = D.Bray_Curtis(o2, t2)
+        actual = 0.8000
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testBray_Curtis3(self):
+        'Bray Curtis for list (Test 2)'
+        distance = D.Bray_Curtis(o3, t3)
+        actual = 0.9474
+        self.assertAlmostEqual(distance, actual, places=4)   
+        
+    def testCosine2(self):
+        'Cosine for list (Test 1)'
+        distance = D.Cosine(o2, t2)
+        actual = 0.8000
+        self.assertAlmostEqual(distance, actual, places=4)
+    
+    def testCosine3(self):
+        'Cosine for list (Test 2)'
+        distance = D.Cosine(o3, t3)
+        actual = 0.9873
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testTanimoto2(self):
+        'Tanimoto for list (Test 1)'
+        distance = D.Tanimoto(o2, t2)
+        actual = 0.6667
+        self.assertAlmostEqual(distance, actual, places=4)
+        
+    def testTanimoto(self):
+        'Tanimoto for list (Test 2)'
+        distance = D.Tanimoto(o3, t3)
+        actual = 0.9738
+        self.assertAlmostEqual(distance, actual, places=4)  
+
+
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file
diff --git a/test/t_statisticsdistribution.py b/test/t_statisticsdistribution.py
new file mode 100644
index 0000000..88f2e84
--- /dev/null
+++ b/test/t_statisticsdistribution.py
@@ -0,0 +1,563 @@
+import sys
+import os
+import unittest
+
+sys.path.append(os.path.join(os.path.dirname(os.getcwd()), 'copads'))
+import statisticsdistribution as N
+
+class testBeta(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python 
+    Papers Source Codes 1:4
+    """
+    def testCDF1(self):
+        p = N.BetaDistribution(location=0, scale=1,
+                p=1, q=2).CDF(1.0)
+        self.assertAlmostEqual(p, 1.0000, places=4)
+    def testCDF2(self):
+        p = N.BetaDistribution(location=0, scale=1,
+                p=6, q=2).CDF(1.0)
+        self.assertAlmostEqual(p, 1.0000, places=4)
+
+class testBinomial(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python 
+    Papers Source Codes 1:4
+    """
+    def testCDF1(self):
+        p = N.BinomialDistribution(trial=1000,
+                success=0.5).CDF(500)
+        self.assertAlmostEqual(p, 0.5126125, places=4)
+    def testPDF1(self):
+        p = N.BinomialDistribution(trial=20,
+                success=0.5).PDF(10)
+        self.assertAlmostEqual(p, 0.1762, places=4)
+    def testPDF2(self):
+        p = N.BinomialDistribution(trial=20, success=0.1).PDF(1)
+        self.assertAlmostEqual(p, 0.27017, places=4)
+    def testPDF3(self):
+        p = N.BinomialDistribution(trial=20, success=0.1).PDF(3)
+        self.assertAlmostEqual(p, 0.19012, places=4)
+    def testPDF4(self):
+        p = N.BinomialDistribution(trial=20, success=0.1).PDF(4)
+        self.assertAlmostEqual(p, 0.08978, places=4)
+        
+        
+class testCauchy(unittest.TestCase):
+    def testCDF1(self):
+        p = N.CauchyDistribution(location = 0.0, scale = 1.0).CDF(0.0)
+        self.assertAlmostEqual(p, 0.5, places=4)
+    def testCDF2(self):
+        p = N.CauchyDistribution(location = 0.0, scale = 1.0).CDF(1.0)
+        self.assertAlmostEqual(p, 0.75, places=4)
+    def testCDF3(self):
+        p = N.CauchyDistribution(location = 0.0, scale = 1.0).CDF(2.0)
+        self.assertAlmostEqual(p, 0.85241, places=4)
+    def testPDF1(self):
+        p = N.CauchyDistribution(location = 0.0, scale = 1.0).PDF(0.0)
+        self.assertAlmostEqual(p, 0.31830, places=4)
+    def testPDF2(self):
+        p = N.CauchyDistribution(location = 0.0, scale = 1.0).PDF(1.0)
+        self.assertAlmostEqual(p, 0.15915, places=4)
+    def testPDF3(self):
+        p = N.CauchyDistribution(location = 0.0, scale = 1.0).PDF(2.0)
+        self.assertAlmostEqual(p, 0.06366, places=4)
+    def testinverseCDF1(self):
+        p = N.CauchyDistribution(location = 0.0, scale = 1.0).inverseCDF(0.5)[0]
+        self.assertAlmostEqual(p, 0)
+    def testinverseCDF2(self):
+        p = N.CauchyDistribution(location = 0.0, scale = 1.0).inverseCDF(0.75)[0]
+        self.assertAlmostEqual(p, 1.0)
+    def testinverseCDF3(self):
+        p = N.CauchyDistribution(location = 0.0, scale = 1.0).inverseCDF(0.8524163)[0]
+        self.assertAlmostEqual(p, 2.0)
+
+
+class testChiSquare(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python 
+    Papers Source Codes 1:4
+    """
+    def testCDF0_1(self):
+        p = N.ChiSquareDistribution(df=1).CDF(2.706)
+        self.assertAlmostEqual(p, 0.9000, places=4)
+    def testCDF0_2(self):
+        p = N.ChiSquareDistribution(df=1).CDF(3.841)
+        self.assertAlmostEqual(p, 0.9500, places=4)
+    def testCDF0_3(self):
+        p = N.ChiSquareDistribution(df=1).CDF(10.828)
+        self.assertAlmostEqual(p, 0.9990, places=4)
+    def testCDF10_1(self):
+        p = N.ChiSquareDistribution(df=10).CDF(15.987)
+        self.assertAlmostEqual(p, 0.9000, places=4)
+    def testCDF10_2(self):
+        p = N.ChiSquareDistribution(df=10).CDF(18.307)
+        self.assertAlmostEqual(p, 0.9500, places=4)
+    def testCDF10_3(self):
+        p = N.ChiSquareDistribution(df=10).CDF(29.588)
+        self.assertAlmostEqual(p, 0.9990, places=4)
+    def testinverseCDF1(self):
+        p = N.ChiSquareDistribution(df=10).inverseCDF(0.9)[0]
+        self.assertAlmostEqual(p, 15.9870, places=2)
+    def testinverseCDF2(self):
+        p = N.ChiSquareDistribution(df=10).inverseCDF(0.95)[0]
+        self.assertAlmostEqual(p, 18.307, places=1)
+    def testinverseCDF3(self):
+        p = N.ChiSquareDistribution(df=10).inverseCDF(0.999)[0]
+        self.assertAlmostEqual(p, 29.588, places=2)
+        
+        
+class testCosine(unittest.TestCase):
+    def testCDF1(self):
+        p = N.CosineDistribution(location = 0.0, scale = 1.0).CDF(0.0)
+        self.assertAlmostEqual(p, 0.5, places=2)
+    def testCDF2(self):
+        p = N.CosineDistribution(location = 0.0, scale = 1.0).CDF(1.0)
+        self.assertAlmostEqual(p, 0.793079, places=4)
+    def testCDF3(self):
+        p = N.CosineDistribution(location = 0.0, scale = 1.0).CDF(10.0)
+        self.assertAlmostEqual(p, 2.00496578, places=4)
+    def testinverseCDF1(self):
+        p = N.CosineDistribution(location = 0.0,
+                                  scale = 1.0).inverseCDF(2.00496578)[0]
+        self.assertAlmostEqual(p, 10.0, places=2)
+    def testinverseCDF2(self):
+        p =  N.CosineDistribution(location = 0.0,
+                                  scale = 1.0).inverseCDF(0.5)[0]
+        self.assertAlmostEqual(p, 0.0, places=2)
+    def testPDF1(self):
+        p = N.CosineDistribution(location = 0.0, scale = 1.0).PDF(0.0)
+        self.assertAlmostEqual(p, 0.318309, places=4)
+    def testPDF2(self):
+        p = N.CosineDistribution(location = 0.0, scale = 1.0).PDF(1.0)
+        self.assertAlmostEqual(p, 0.245147, places=4)
+    def testPDF3(self):
+        p = N.CosineDistribution(location = 0.0, scale = 1.0).PDF(10.0)
+        self.assertAlmostEqual(p, 0.02561256, places=4)
+    def testVariance(self):
+        p = N.CosineDistribution(location = 0.0, scale = 1.0).variance()
+        self.assertAlmostEqual(p, 1.289868, places=4)
+ 
+        
+class testExponential(unittest.TestCase):
+    def testCDF1(self):
+        p = N.ExponentialDistribution(location = 0.0, scale = 1.0).CDF(0.0)
+        self.assertAlmostEqual(p, 0.0, places = 2)
+    def testCDF2(self):
+        p = N.ExponentialDistribution(location = 0.0, scale = 1.0).CDF(2.0)
+        self.assertAlmostEqual(p, 0.86466, places = 4)
+    def testCDF3(self):
+        p = N.ExponentialDistribution(location = 1.0, scale = 1.0).CDF(0.0)
+        self.assertAlmostEqual(p, - 1.7182818, places = 4)
+    def testPDF1(self):
+        p = N.ExponentialDistribution(location = 0.0, scale = 1.0).PDF(0.0)
+        self.assertAlmostEqual(p, 1.0, places = 4)
+    def testPDF2(self):
+        p = N.ExponentialDistribution(location = 0.0, scale = 1.0).PDF(1.0)
+        self.assertAlmostEqual(p, 0.3679, places = 4)
+    def testvariance(self):
+        p = N.ExponentialDistribution(location = 0.0, scale = 1.0).variance()
+        self.assertAlmostEqual(p, 1.0, places = 4)
+    def testmean(self):
+        p = N.ExponentialDistribution(location = 0.0, scale = 1.0).mean()
+        self.assertAlmostEqual(p, 1.0, places = 4)
+    def testmedian(self):
+        p = N.ExponentialDistribution(location = 0.0, scale = 1.0).median()
+        self.assertAlmostEqual(p, 0.30103, places = 4)
+ 
+        
+class testF(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python 
+    Papers Source Codes 1:4
+    """
+    def testCDF1(self):
+        p = N.FDistribution(df1=3, df2=5).CDF(1.0)
+        self.assertAlmostEqual(p, 0.535145, places=4)
+    def testCDF2(self):
+        p = N.FDistribution(df1=3, df2=5).CDF(2.0)
+        self.assertAlmostEqual(p, 0.767376, places=4)
+    def testCDF3(self):
+        p = N.FDistribution(df1=3, df2=5).CDF(3.0)
+        self.assertAlmostEqual(p, 0.866145, places=4)
+    def testPDF1(self):
+        p = N.FDistribution(df1=3, df2=5).PDF(1.0)
+        self.assertAlmostEqual(p, 0.361174, places=4)
+    def testPDF2(self):
+        p = N.FDistribution(df1=3, df2=5).PDF(2.0)
+        self.assertAlmostEqual(p, 0.1428963, places=4)
+    def testPDF3(self):
+        p = N.FDistribution(df1=3, df2=5).PDF(3.0)
+        self.assertAlmostEqual(p, 0.066699, places=4)
+    def testinverseCDF1(self):
+        p = N.FDistribution(df1=3,
+                df2=5).inverseCDF(0.535145)[0]
+        self.assertAlmostEqual(p, 1.0000, places=2)
+    def testinverseCDF2(self):
+        p = N.FDistribution(df1=3,
+                df2=5).inverseCDF(0.767376)[0]
+        self.assertAlmostEqual(p, 2.00, places=2)
+    def testinverseCDF3(self):
+        p = N.FDistribution(df1=3,
+                df2=5).inverseCDF(0.866145)[0]
+        self.assertAlmostEqual(p, 3.00, places=2)
+
+
+class testGamma(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python 
+    Papers Source Codes 1:4
+    """
+    def testCDF1(self):
+        p = N.GammaDistribution(location=0, scale=4,
+                shape=4).CDF(7.0)
+        self.assertAlmostEqual(p, 0.1008103, places=4)
+    def testCDF2(self):
+        p = N.GammaDistribution(location=0, scale=4,
+                shape=4).CDF(7.5)
+        self.assertAlmostEqual(p, 0.1210543, places=4)
+    def testCDF3(self):
+        p = N.GammaDistribution(location=0, scale=4,
+                shape=4).CDF(8.0)
+        self.assertAlmostEqual(p, 0.1428765, places=4)
+    def testinverseCDF1(self):
+        p = N.GammaDistribution(location=0, scale=4,
+                                shape=4).inverseCDF(0.1008103)[0]
+        self.assertAlmostEqual(p, 7.0000, places=4)
+    def testinverseCDF2(self):
+        p = N.GammaDistribution(location=0, scale=4,
+                                shape=4).inverseCDF(0.1210543)[0]
+        self.assertAlmostEqual(p, 7.5000, places=4)
+    def testinverseCDF3(self):
+        p = N.GammaDistribution(location=0, scale=4,
+                                shape=4).inverseCDF(0.1428765)[0]
+        self.assertAlmostEqual(p, 8.00, places=2)
+    def test_kurtosis(self):
+        p = N.GammaDistribution(location=0, scale=4,
+                                shape=4).kurtosis()
+        self.assertAlmostEqual(p, 1.50, places=2)
+        
+##
+##class testGumbel(unittest.TestCase):
+##    def testCDF(self):
+##        pass
+##    def testPDF(self):
+##        pass
+##    def testinverseCDF(self):
+##        pass
+##      
+
+
+##class testLogNormal(unittest.TestCase):
+##    def testCDF(self):
+##        pass
+##    def testPDF(self):
+##        pass
+##    def testinverseCDF(self):
+##        pass
+##
+##class testNegativeBinomial(unittest.TestCase):
+##    def testCDF(self):
+##        pass
+##    def testPDF(self):
+##        pass
+##    def testinverseCDF(self):
+##        pass
+
+class testGeometric(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python 
+    Papers Source Codes 1:4
+    """
+    def testCDF1(self):
+        p = N.GeometricDistribution(success=0.4).CDF(1)
+        self.assertAlmostEqual(p, 0.4000, places=4)
+    def testCDF2(self):
+        p = N.GeometricDistribution(success=0.4).CDF(4)
+        self.assertAlmostEqual(p, 0.8704, places=4)
+    def testCDF3(self):
+        p = N.GeometricDistribution(success=0.4).CDF(6)
+        self.assertAlmostEqual(p, 0.953344, places=4)
+    def testPDF1(self):
+        p = N.GeometricDistribution(success=0.4).PDF(1)
+        self.assertAlmostEqual(p, 0.4000, places=4)
+    def testPDF2(self):
+        p = N.GeometricDistribution(success=0.4).PDF(4)
+        self.assertAlmostEqual(p, 0.0864, places=4)
+    def testPDF3(self):
+        p = N.GeometricDistribution(success=0.4).PDF(6)
+        self.assertAlmostEqual(p, 0.031104, places=4)
+    def testinverseCDF1(self):
+        p = N.GeometricDistribution(
+                success=0.4).inverseCDF(0.4)[0]
+        self.assertAlmostEqual(p, 1.0000, places=4)
+    def testinverseCDF2(self):
+        p = N.GeometricDistribution(
+                success=0.4).inverseCDF(0.8704)[0]
+        self.assertAlmostEqual(p, 4.0000, places=4)
+    def testinverseCDF3(self):
+        p = N.GeometricDistribution(
+                success=0.4).inverseCDF(0.953344)[0]
+        self.assertAlmostEqual(p, 6.0000, places=4)
+
+        
+class testHypergeometric(unittest.TestCase):
+    def testPDF(self):
+        p = N.HypergeometricDistribution(sample_size=10,
+                                population_size=100, 
+                                population_success=50).PDF(5)
+        self.assertAlmostEqual(p, 0.259333,  places = 2)
+    def testCDF(self):
+        p = N.HypergeometricDistribution(sample_size=10,
+                                population_size=100, 
+                                population_success=50).CDF(5)
+        self.assertAlmostEqual(p, 0.629073,  places = 2)
+    def testinverseCDF(self):
+        p = N.HypergeometricDistribution(sample_size=10,
+                            population_size=100, 
+                            population_success=50).inverseCDF(0.629073)[0]
+        self.assertAlmostEqual(p, 5,  places = 2)
+    def testmean(self):
+        p = N.HypergeometricDistribution(sample_size=10,
+                                population_size=100, 
+                                population_success=50).mean()
+        self.assertAlmostEqual(p, 5.000,  places = 2)
+    def testmode(self):
+        p = N.HypergeometricDistribution(sample_size=10,
+                                population_size=100, 
+                                population_success=50).mode()
+        self.assertAlmostEqual(p, 5.500,  places = 2)
+    def testvariance(self):
+        p = N.HypergeometricDistribution(sample_size=10,
+                                population_size=100, 
+                                population_success=50).variance()
+        self.assertAlmostEqual(p, 2.272727,  places = 2)
+
+        
+class testLogarithmic(unittest.TestCase):
+    def testPDF(self):
+        p = N.LogarithmicDistribution(shape=0.45).PDF(1.0)
+        self.assertAlmostEqual(p, 1.7332, places=4)
+    def testCDF(self):
+        p = N.LogarithmicDistribution(shape=0.45).CDF(0.0)
+        self.assertAlmostEqual(p, 0.0, places=2)
+    def testinverseCDF(self):
+        p = N.LogarithmicDistribution(shape=0.45).CDF(0.0)
+        self.assertAlmostEqual(p,0.0, places=2)
+    def testmode(self):
+        p = N.LogarithmicDistribution(shape=0.45).mode()
+        self.assertAlmostEqual(p, 1.0, places=2)
+    def testmean(self):
+        p = N.LogarithmicDistribution(shape=0.45).mean()
+        self.assertAlmostEqual(p, 3.15124, places=4)
+
+        
+class testNormal(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Ten Z-Test Routines from Gopal Kanji's 100 
+    Statistical Tests. The Python Papers Source Codes 1:5
+    """
+    def testCDF1(self):
+        p = N.NormalDistribution().CDF(0)
+        self.assertTrue(abs(p/0.5 - 1) < 0.01)
+    def testPDF1(self):
+        p = N.NormalDistribution().PDF(0)
+        self.assertTrue(abs(p/0.3989423 - 1) < 0.01)
+    def testinverseCDF1(self):
+        p = N.NormalDistribution().inverseCDF(0.5)[0]
+        self.assertTrue(abs(p) < 0.01)
+
+        
+class testPoisson(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python 
+    Papers Source Codes 1:4
+    """
+    def testCDF5_1(self):
+        p = N.PoissonDistribution(expectation=5).CDF(1)
+        self.assertAlmostEqual(p, 0.04, places=2)
+    def testCDF5_2(self):
+        p = N.PoissonDistribution(expectation=5).CDF(10)
+        self.assertAlmostEqual(p, 0.986, places=3)
+    def testCDF7_1(self):
+        p = N.PoissonDistribution(expectation=7).CDF(10)
+        self.assertAlmostEqual(p, 0.901, places=3)
+    def testCDF7_2(self):
+        p = N.PoissonDistribution(expectation=7).CDF(13)
+        self.assertAlmostEqual(p, 0.987, places=3)
+    def testinverseCDF5_1(self):
+        x = N.PoissonDistribution(
+                expectation=5).inverseCDF(0.04)[0]
+        self.assertAlmostEqual(x, 1.000, places=2)
+    def testinverseCDF5_2(self):
+        x = N.PoissonDistribution(
+                expectation=5).inverseCDF(0.986)[0]
+        self.assertAlmostEqual(x, 10.000, places=2)
+    def testinverseCDF7_1(self):
+        x = N.PoissonDistribution(
+                expectation=7).inverseCDF(0.901)[0]
+        self.assertAlmostEqual(x, 10.000, places=2)
+
+        
+class testSemicircular(unittest.TestCase):
+    def testPDF1(self):
+        p = N.SemicircularDistribution(location=0.0, scale=1.0).PDF(0.0)
+        self.assertAlmostEqual(p, 0.63662, places=4)
+    def testPDF2(self):
+        p = N.SemicircularDistribution(location=0.0, scale=1.0).PDF(0.5)
+        self.assertAlmostEqual(p, 0.55133, places=4)
+    def testCDF1(self):
+        p = N.SemicircularDistribution(location=0.0, scale=1.0).CDF(0.0)
+        self.assertAlmostEqual(p, 0.5, places=4)
+    def testCDF2(self):
+        p = N.SemicircularDistribution(location=0.0, scale=1.0).CDF(0.5)
+        self.assertAlmostEqual(p, 0.804498, places = 4)
+    def testinverseCDF(self):
+        p = N.SemicircularDistribution(location=0.0,
+                                       scale=1.0).inverseCDF(0.5)[0]
+        self.assertAlmostEqual(p, 0.0, places = 2)
+    
+    
+class testT(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python 
+    Papers Source Codes 1:4
+    """
+    def testinverseCDF1_1(self):
+        x = N.TDistribution(location=0.0, scale=1.0,
+                            shape=2).inverseCDF(0.9)[0]
+        self.assertAlmostEqual(x, 1.886, places=1)
+    def testinverseCDF1_2(self):
+        x = N.TDistribution(location=0.0, scale=1.0,
+                            shape=2).inverseCDF(0.95)[0]
+        self.assertAlmostEqual(x, 2.920, places=3)
+    def testinverseCDF1_3(self):
+        x = N.TDistribution(location=0.0, scale=1.0,
+                            shape=2).inverseCDF(0.99)[0]
+        self.assertAlmostEqual(x, 6.965, places=1)
+    def testinverseCDF1_4(self):
+        x = N.TDistribution(location=0.0, scale=1.0,
+                            shape=2).inverseCDF(0.999)[0]
+        self.assertAlmostEqual(x, 22.328, places=1)
+    def testinverseCDF5_1(self):
+        x = N.TDistribution(location=0.0, scale=1.0,
+                            shape=5).inverseCDF(0.9)[0]
+        self.assertAlmostEqual(x, 1.476, places=3)
+    def testinverseCDF5_2(self):
+        x = N.TDistribution(location=0.0, scale=1.0,
+                            shape=5).inverseCDF(0.95)[0]
+        self.assertAlmostEqual(x, 2.020, places=2)
+    def testinverseCDF5_3(self):
+        x = N.TDistribution(location=0.0, scale=1.0,
+                            shape=5).inverseCDF(0.99)[0]
+        self.assertAlmostEqual(x, 3.365, places=2)
+    def testinverseCDF5_4(self):
+        x = N.TDistribution(location=0.0, scale=1.0,
+                            shape=5).inverseCDF(0.999)[0]
+        self.assertAlmostEqual(x, 5.893, places=2)
+        
+    
+class testTriangular(unittest.TestCase):
+    def testCDF1(self):
+       p = N.TriangularDistribution(2.0, 1.0).CDF(1.0)
+       self.assertAlmostEqual(p, 0.5000, places=4)
+    def testCDF2(self):
+       p = N.TriangularDistribution(2.0, 1.0).CDF(0.8)
+       self.assertAlmostEqual(p, 0.3200, places=4)
+    def testCDF3(self):
+       p = N.TriangularDistribution(4.0, 2.0, -4).CDF(1.0)
+       self.assertAlmostEqual(p, 0.5208, places=4)
+    def testPDF1(self):
+       p = N.TriangularDistribution(2.0, 1.0).PDF(0.08)
+       self.assertAlmostEqual(p, 0.08000, places=4)
+    def testPDF2(self):
+       p = N.TriangularDistribution(4.0, 2.0, -4).PDF(1.0)
+       self.assertAlmostEqual(p, 0.20833, places=4)
+    def testkurtosis(self):
+       p = N.TriangularDistribution(2.0, 1.0).kurtosis()
+       self.assertAlmostEqual(p, -0.6, places=4)
+
+       
+class testUniform(unittest.TestCase):
+    """
+    @see: Ling, MHT. 2009. Compendium of Distributions, I: Beta, Binomial, Chi-
+    Square, F, Gamma, Geometric, Poisson, Student's t, and Uniform. The Python 
+    Papers Source Codes 1:4
+    """
+    def testCDF1(self):
+        p = N.UniformDistribution(location=1.0,
+                scale=3.0).CDF(1.5)
+        self.assertTrue(abs(p / 0.25 - 1) < 0.01)
+        self.assertAlmostEqual(p, 0.2500, places=4)
+    def testCDF2(self):
+        p = N.UniformDistribution(location=1.0,
+                scale=3.0).CDF(2.5)
+        self.assertTrue(abs(p / 0.75 - 1) < 0.01)
+        self.assertAlmostEqual(p, 0.7500, places=4)
+    def testCDF3(self):
+        p = N.UniformDistribution(location=-1.0,
+                                  scale=1.0).CDF(0)
+        self.assertAlmostEqual(p, 0.5)
+    def testPDF1(self):
+        p = N.UniformDistribution(location=1.0,
+                scale=3.0).PDF()
+        self.assertTrue(p == 0.5)
+    def testPDF2(self):
+        p = N.UniformDistribution(location=1.0,
+                scale=3.0).PDF()
+        self.assertTrue(p == 0.5)
+    def testinverseCDF1(self):
+        p = N.UniformDistribution(location=1.0,
+                scale=3.0).inverseCDF(0.25)[0]
+        self.assertAlmostEqual(p, 1.50, places=1)
+    def testinverseCDF2(self):
+        p = N.UniformDistribution(location=1.0,
+                scale=3.0).inverseCDF(0.75)[0]
+        self.assertAlmostEqual(p, 2.50, places=1)
+    def test_mean(self):
+        p = N.UniformDistribution(location=1,
+                    scale=2).mean()
+        self.assertAlmostEqual(p, 1.5)
+
+        
+class testWeibull(unittest.TestCase):
+    def testCDF1(self):
+        p = N.WeiBullDistribution(location=1.0, 
+                    scale=1.0).CDF(2)
+        self.assertAlmostEqual(p, 0.864664, places=5)
+    def testCDF2(self):
+        p = N.WeiBullDistribution(location=2.0, 
+                    scale=2.0).CDF(2)
+        self.assertAlmostEqual(p, 0.632120, places=5)
+    def testPDF1(self):
+        p = N.WeiBullDistribution(location=1.0, 
+                    scale=1.0).PDF(2)
+        self.assertAlmostEqual(p, 0.135335, places=5)
+    def testinverseCDF1(self):
+        p = N.WeiBullDistribution(location=1.0, 
+                    scale=1.0).inverseCDF(0.864664)[0]
+        self.assertAlmostEqual(p, 2.000000, places=5)
+    def testinverseCDF2(self):
+        p = N.WeiBullDistribution(location=2.0, 
+                    scale=2.0).inverseCDF(0.632120)[0]
+        self.assertAlmostEqual(p, 2.000000, places=5)
+    def testmedian(self):
+        p = N.WeiBullDistribution(location=2.0, 
+                    scale=2.0).median()
+        self.assertAlmostEqual(p, 1.665109, places=5)
+    def testmode(self):
+        p = N.WeiBullDistribution(location=2.0, 
+                    scale=2.0).mode()
+        self.assertAlmostEqual(p, 1.414213, places=5)
+
+        
+if __name__ == '__main__':
+    unittest.main()
\ No newline at end of file