Author: bugman Date: Mon Mar 19 10:13:21 2012 New Revision: 15537 URL: http://svn.gna.org/viewcvs/relax?rev=15537&view=rev Log: Started to create a second script to demonstrate a working implementation of the multi package. Added: 1.3/multi/test_implementation2.py - copied, changed from r15475, 1.3/multi/test_implementation.py Modified: 1.3/test_suite/unit_tests/_multi/test___init__.py Copied: 1.3/multi/test_implementation2.py (from r15475, 1.3/multi/test_implementation.py) URL: http://svn.gna.org/viewcvs/relax/1.3/multi/test_implementation2.py?p2=1.3/multi/test_implementation2.py&p1=1.3/multi/test_implementation.py&r1=15475&r2=15537&rev=15537&view=diff ============================================================================== --- 1.3/multi/test_implementation.py (original) +++ 1.3/multi/test_implementation2.py Mon Mar 19 10:13:21 2012 @@ -1,47 +1,28 @@ -"""A reference implementation of the multi-processor package. +"""A reference implementation of the multi-processor package demonstrating pre-sending of data. Description =========== -This is a basic but full implementation of the multi-processor package to demonstrate how it is used. +This implementation of the multi-processor package is used to demonstrate how to pre-send invariant data once to all slaves to minimise the data transfer for each parallelised calculation block. Testing ======= -To run in uni-processor mode on a dual core system, change the MULTI variable to False and type:: - -$ python test_implementation.py - - -To run in mpi4py multi-processor mode with one master and two slave processors on minimally a dual core system, change the MULTI variable to True and type:: - -$ mpiexec -n 3 python test_implementation.py - -For a single dual core CPU (Intel Core 2 Duo E8400 at 3.00GHz), the total times averaged over 5 runs are: - - Uni-processor: 51.548 seconds (51.054+52.224+51.257+51.112+52.093) - - Mpi4py-processor: 28.008 seconds (30.719+27.218+27.799+26.343+27.961) - - Scaling efficiency: 1.840 - - -Linux ------ - -For multi-core systems and Linux 2.6, the following might be required to prevent the master processor from taking 100% of one CPU core while waiting for the slaves: - -# echo "1" > /proc/sys/kernel/sched_compat_yield - -This appears to be an OpenMPI problem with late 2.6 Linux kernels. +To run in mpi4py multi-processor mode with one master and two slave processors, type:: + +$ mpiexec -n 3 python test_implementation2.py """ # Python module imports. -from numpy import dot, float64, zeros +from numpy import array, dot, float64, zeros +from numpy.linalg import norm try: import cProfile as profile except ImportError: import profile import pstats -from random import uniform +from random import gauss import sys # Modify the module path. @@ -52,14 +33,8 @@ # Module variables. -PROFILE = True -MULTI = True -if MULTI: - FABRIC = 'mpi4py' - PROCESSOR_NUM = 2 -else: - FABRIC = 'uni' - PROCESSOR_NUM = 1 +FABRIC = 'mpi4py' +PROCESSOR_NUM = 2 def print_stats(stats, status=0): @@ -69,6 +44,72 @@ pstats.Stats(stats).sort_stats('cumulative').print_stats() +def quaternion_to_R(quat, R): + """Convert a quaternion into rotation matrix form. + + This is from Wikipedia (http://en.wikipedia.org/wiki/Rotation_matrix#Quaternion), where:: + + | 1 - 2y**2 - 2z**2 2xy - 2zw 2xz + 2yw | + Q = | 2xy + 2zw 1 - 2x**2 - 2z**2 2yz - 2xw |, + | 2xz - 2yw 2yz + 2xw 1 - 2x**2 - 2y**2 | + + and where the quaternion is defined as q = (w, x, y, z). This has been verified using Simo + Saerkkae's "Notes on Quaternions" at http://www.lce.hut.fi/~ssarkka/. + + + @param quat: The quaternion. + @type quat: numpy 4D, rank-1 array + @param R: A 3D matrix to convert to the rotation matrix. + @type R: numpy 3D, rank-2 array + """ + + # Alias. + (w, x, y, z) = quat + + # Repetitive calculations. + x2 = 2.0 * x**2 + y2 = 2.0 * y**2 + z2 = 2.0 * z**2 + xw = 2.0 * x*w + xy = 2.0 * x*y + xz = 2.0 * x*z + yw = 2.0 * y*w + yz = 2.0 * y*z + zw = 2.0 * z*w + + # The diagonal. + R[0, 0] = 1.0 - y2 - z2 + R[1, 1] = 1.0 - x2 - z2 + R[2, 2] = 1.0 - x2 - y2 + + # The off-diagonal. + R[0, 1] = xy - zw + R[0, 2] = xz + yw + R[1, 2] = yz - xw + + R[1, 0] = xy + zw + R[2, 0] = xz - yw + R[2, 1] = yz + xw + + +def R_random_hypersphere(R): + """Generate a random rotation matrix using 4D hypersphere point picking. + + A quaternion is generated by creating a 4D vector with each value randomly selected from a + Gaussian distribution, and then normalising. + + @param R: A 3D matrix to convert to the rotation matrix. + @type R: numpy 3D, rank-2 array + """ + + # The quaternion. + quat = array([gauss(0, 1), gauss(0, 1), gauss(0, 1), gauss(0, 1)], float64) + quat = quat / norm(quat) + + # Convert the quaternion to a rotation matrix. + quaternion_to_R(quat, R) + + class Main: """The program.""" @@ -77,17 +118,24 @@ """Set up some initial variables.""" # The total number of calculations to perform by all slave processors. - self.N = 2000000 + self.N = 20000 # Variable for counting the completed calculations (to demonstrate slave->master communication). self.num = 0 + # The invariant data to pass to the slaves once. + self.real_length = 2.0 + self.vect = array([1, 2, 3], float64) + self.vect = self.vect / norm(self.vect) * self.real_length + def run(self): """This required method executes the entire program.""" # Initialise the Processor box singleton. processor_box = Processor_box() + + print "\n\nHELLO: ", processor_box.processor.rank(), self.vect # Loop over the slaves. num = processor_box.processor.processor_size() @@ -181,26 +229,22 @@ -class Test_slave_command(Slave_command): - """The slave command for use by the slave processor.""" - - def __init__(self, N=0): +class Test_slave_command_presend(Slave_command): + """The slave command used to pre-send data to the slave processors.""" + + def __init__(self, vect=None): """Set up the slave command object for the slave processor. - @keyword N: The number of calculations for the slave to perform. - @type N: int + @keyword vect: The invariant vector used in all the slave calculations. + @type vect: int """ # Store the argument. - self.N = N - - # Initialise some matrices. - self.A = zeros((3, 3), float64) - self.B = zeros((3, 3), float64) + self.vect = vect def run(self, processor, completed=False): - """Essential method for performing calculations on the slave processors. + """Do nothing. @param processor: The slave processor object. @type processor: Processor instance @@ -208,17 +252,46 @@ @type completed: bool """ + + +class Test_slave_command(Slave_command): + """The slave command for use by the slave processor.""" + + def __init__(self, N=0): + """Set up the slave command object for the slave processor. + + @keyword N: The number of calculations for the slave to perform. + @type N: int + """ + + # Store the argument. + self.N = N + + # Initialise the rotation matrix. + self.R = zeros((3, 3), float64) + + + def run(self, processor, completed=False): + """Essential method for performing calculations on the slave processors. + + @param processor: The slave processor object. + @type processor: Processor instance + @keyword completed: A flag specifying if the slave calculation is completed. This is currently meaningless, but will be passed to this run() method anyway so it needs to be present. + @type completed: bool + """ + # Perform some random useless time-consuming stuff. num_calcs = 0 for i in range(self.N): - # Randomise the matrices. - for j in range(3): - for k in range(3): - self.A[j, k] = uniform(0, 1) - self.B[j, k] = uniform(0, 1) - - # Perform some linear algebra. - dot(self.A, self.B) + # Random rotation matrix. + R_random_hypersphere(self.R) + + continue + # Rotate the vector. + new_vect = dot(self.R, self.vect) + + # The length sum. + self.length += norm(new_vect) # Keep track of the number of calculations. num_calcs += 1 @@ -231,14 +304,10 @@ # Set up the processor. processor = load_multiprocessor(FABRIC, Application_callback(master=Main()), processor_size=PROCESSOR_NUM, verbosity=1) -# Run in multi-processor mode. -if not PROFILE: - processor.run() - -# Run in multi-processor mode with profiling. -else: - # Replace the default profiling print out function. - profile.Profile.print_stats = print_stats - - # Execute with profiling. - profile.runctx('processor.run()', globals(), locals()) +processor.run() + +# Replace the default profiling print out function. +profile.Profile.print_stats = print_stats + +# Execute with profiling. +#profile.runctx('processor.run()', globals(), locals()) Modified: 1.3/test_suite/unit_tests/_multi/test___init__.py URL: http://svn.gna.org/viewcvs/relax/1.3/test_suite/unit_tests/_multi/test___init__.py?rev=15537&r1=15536&r2=15537&view=diff ============================================================================== --- 1.3/test_suite/unit_tests/_multi/test___init__.py (original) +++ 1.3/test_suite/unit_tests/_multi/test___init__.py Mon Mar 19 10:13:21 2012 @@ -40,4 +40,4 @@ self.package_path = sys.path[0] + sep + 'multi' # Blacklisted scripts. - self.blacklist = ['test_implementation.py'] + self.blacklist = ['test_implementation.py', 'test_implementation2.py']