RoadRunner with Multiprocessing

For these simulations we use a simple model that we can access easily from one of RoadRunners test suites. Its called BatchImmigrationDeath03 and is model number 00039 from the stochastic sbml test suite.

In each of the following sections we use RoadRunner to generate 1e6 stochastic simulations.

Serial code

Performing the simulations in serial gives us a way to measure the performance enhancements of using parallel code.

 1import sys
 2sys.path += [
 3    r'D:\roadrunner\roadrunner\roadrunner-install-rel-llvm6\site-packages',
 4    # r'D:\roadrunner\roadrunner\roadrunner-install-rel-llvm12\site-packages'
 5]
 6
 7import roadrunner
 8assert "llvm6" in roadrunner.__file__, roadrunner.__file__
 9
10from roadrunner import RoadRunner
11from roadrunner.tests import TestModelFactory as tmf
12import time
13import numpy as np
14from platform import platform
15import cpuinfo # pip install py-cpuinfo
16
17
18
19NSIMS = 1000000
20
21if __name__ == '__main__':
22    # setup timing
23    start = time.time()
24
25    # get sbml to work with from one of our test modules
26    sbml = tmf.BatchImmigrationDeath03().str()
27
28    # create our roadrunner instance
29    rr = RoadRunner(sbml)
30
31    # set up a stochastic simulation
32    rr.setIntegrator('gillespie')
33
34    # set the seed for reproducible example
35    gillespie_integrator = rr.getIntegrator()
36    gillespie_integrator.seed = 1234
37
38    start_time = 0
39    end_time = 10
40    num_points = 11
41
42    # preallocate for efficiency
43    data = np.ndarray((NSIMS, num_points, 2))
44    for simulation_number in range(NSIMS):
45        rr.resetAll()
46        data[simulation_number] = rr.simulate(start_time, end_time, num_points)
47
48    print(data)
49    print(data.shape)
50
51    print('Took',  time.time() - start, 'seconds to run', NSIMS, 'stochastic simulations on 1 core')
52    cpu_info = cpuinfo.get_cpu_info()
53    print(f'Platform: {platform()}')
54    print('python_version:', cpu_info['python_version'])
55    print('Processor:', cpu_info['brand_raw'])
56
57    '''
58    Output:
59        Took 64.92753291130066 seconds to run 1000000 stochastic simulations on 1 core
60
61        Platform: Windows-10-10.0.22000-SP0
62        python_version: 3.9.5.final.0 (64 bit)
63        Processor: 11th Gen Intel(R) Core(TM) i9-11980HK @ 2.60GHz
64    '''

Multiprocessing library

The simplest way in Python to use multiple cores simultaneously for running RoadRunner simulations is to use the multiprocessing library. Further, the simplest multiprocessing tool that one can use is a multiprocessing.Pool. This leads to about a x3 speed up for this example.

 1from roadrunner import RoadRunner
 2from roadrunner.tests import TestModelFactory as tmf
 3from multiprocessing import Pool, cpu_count
 4import time
 5from platform import platform
 6import cpuinfo # pip install py-cpuinfo
 7
 8NCORES = cpu_count()
 9NSIMS = 1000000
10
11def simulate_worker(rr: RoadRunner):
12    rr.resetAll()
13    return rr.simulate(0, 10, 11)
14
15
16if __name__ == '__main__':
17    # setup timing
18    start = time.time()
19
20    # get sbml to work with from one of our test modules
21    sbml = tmf.BatchImmigrationDeath03().str()
22
23    # create our roadrunner instance
24    rr = RoadRunner(sbml)
25
26    # set up a stochastic simulation
27    rr.setIntegrator('gillespie')
28
29    # set the seed for reproducuble example
30    gillespie_integrator = rr.getIntegrator()
31    gillespie_integrator.seed = 1234
32
33    # create a processing pool
34    p = Pool(processes=NCORES)
35
36    # perform the simulations
37    arrays = p.map(simulate_worker, [rr for i in range(NSIMS)])
38
39    duration = time.time() - start
40
41    # the time it took in serial
42    serial_time = 64.92753291130066
43
44    # compute speedup
45    speedup = serial_time / duration
46
47    print(f'Took {duration} seconds to run', NSIMS, 'stochastic simulations on', NCORES, 'cores')
48    print(f'Speed up is {speedup}')
49    cpu_info = cpuinfo.get_cpu_info()
50    print(f'Platform: {platform()}')
51    print('python_version:', cpu_info['python_version'])
52    print('Processor:', cpu_info['brand_raw'])
53
54    '''
55    Output: 
56        Took 19.231333017349243 seconds to run 1000000 stochastic simulations on 16 cores
57        Speed up is 3.3761327336346008
58        Platform: Windows-10-10.0.22000-SP0
59        python_version: 3.9.5.final.0 (64 bit)
60        Processor: 11th Gen Intel(R) Core(TM) i9-11980HK @ 2.60GHz
61    '''

Ray library

 1import numpy as np
 2
 3from roadrunner import RoadRunner
 4from roadrunner.tests import TestModelFactory as tmf
 5from multiprocessing import cpu_count
 6import ray
 7import time
 8from platform import platform
 9import cpuinfo  # pip install py-cpuinfo
10
11NCORES = cpu_count()
12NSIMS = 1000000
13
14ray.init(ignore_reinit_error=True)
15
16
17@ray.remote
18class SimulatorActorPath(object):
19    """Ray actor to execute simulations."""
20
21    def __init__(self, rr: RoadRunner):
22        self.rr: RoadRunner = rr
23
24    def simulate(self, size=1):
25        num_points = 101
26        results = np.ndarray((size, num_points, 2))  # 2 for 1 model species and time
27        for k in range(size):
28            self.rr.resetAll()
29            results[k] = self.rr.simulate(0, 100, num_points)
30        return results
31
32
33if __name__ == '__main__':
34    # setup timing
35    start = time.time()
36
37    # get sbml to work with from one of our test modules
38    sbml = tmf.BatchImmigrationDeath03().str()
39
40    # create our roadrunner instance
41    rr = RoadRunner(sbml)
42
43    # set up a stochastic simulation
44    rr.setIntegrator('gillespie')
45
46    # set the seed for reproducuble example
47    gillespie_integrator = rr.getIntegrator()
48    gillespie_integrator.seed = 1234
49
50    simulators = [SimulatorActorPath.remote(rr) for _ in range(NCORES)]
51
52    # run simulations
53    tc_ids = []
54    for k, simulator in enumerate(simulators):
55        tcs_id = simulator.simulate.remote(size=int(np.floor(NSIMS / NCORES)))
56        tc_ids.append(tcs_id)
57    results = ray.get(tc_ids)
58    print(results)
59
60    duration = time.time() - start
61
62    # the time it took in serial
63    serial_time = 64.92753291130066
64
65    # compute speedup
66    speedup = serial_time / duration
67
68    print(f'Took {duration} seconds to run', NSIMS, 'stochastic simulations on', NCORES, 'cores')
69    print(f'Speed up is {speedup}')
70    cpu_info = cpuinfo.get_cpu_info()
71    print(f'Platform: {platform()}')
72    print('python_version:', cpu_info['python_version'])
73    print('Processor:', cpu_info['brand_raw'])
74
75    '''
76    Output: 
77        Took 99.32935857772827 seconds to run 1000000 stochastic simulations on 16 cores
78        Speed up is 0.6536590373780867
79        Platform: Windows-10-10.0.22000-SP0
80        python_version: 3.9.5.final.0 (64 bit)
81        Processor: 11th Gen Intel(R) Core(TM) i9-11980HK @ 2.60GHz
82    '''