Parallel RoadRunner

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.

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

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.testing 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(r: RoadRunner):
12    r.resetAll()
13    return r.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    r = RoadRunner(sbml)
25
26    # set up a stochastic simulation
27    r.setIntegrator('gillespie')
28
29    # set the seed for reproducuble example
30    gillespie_integrator = r.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, [r 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.testing 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, r: RoadRunner):
22        self.r: RoadRunner = r
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.r.resetAll()
29            results[k] = self.r.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    r = RoadRunner(sbml)
42
43    # set up a stochastic simulation
44    r.setIntegrator('gillespie')
45
46    # set the seed for reproducuble example
47    gillespie_integrator = r.getIntegrator()
48    gillespie_integrator.seed = 1234
49
50    simulators = [SimulatorActorPath.remote(r) 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    '''