tudat-team / tudat-space

An introduction and guide to tudat-space.
https://tudat-space.readthedocs.io
13 stars 16 forks source link

Improve discussion on multi-threading in "Optimisation with Pygmo" #73

Open gaffarelj opened 2 years ago

gaffarelj commented 2 years ago

In the Optimization with PyGMO section of the docs, I think it would help to add a small discussion about how to properly multi-thread with Python and Pygmo.

I find multi-threading really useful when dealing with astrodynamics optimisation problems since multiple tudat propagations can then be run in batch, allowing for batch fitness evaluations, instead of running all simulations one after another.

Here are a few elements that I have tested in Linux, and that allow for multi-threading in Pygmo without encountering any issues with neither Pygmo, nor tudat, nor Spice:

An example of all of this implemented can be found in my repo, with my own run_opti.py (I called run_problem.py), and my own opti_framework.py (I called drag_comp_problem.py) files.

gaffarelj commented 1 year ago

Just to add to this: parallelizing simulation outside of the Pygmo framework is quite straightforward. I pieced together the essentials here below (wich works with tudatPy and SPICE):

import multiprocessing as MP
import numpy as np

from tudatpy.kernel.numerical_simulation import environment_setup, propagation_setup
from tudatpy.kernel.interface import spice # This setup works with SPICE :)

def run_simulation(arg_1, arg_2):
    # Do some tudat things...
    # Even better, save results directly into a database using sqlite3 to avoid 
    return 1, arg_1 + arg_2

if __name__ == "__main__":
    # Number of simulations to run
    N = 500
    arg_1_list = np.random.normal(-100, 50, size=N)
    arg_2_list = np.random.normal(1e6, 2e5, size=N)

    # Combine list of inputs
    inputs = []
    for i in range(N):
        inputs.append((arg_1_list[i], arg_2_list[i]))

    # Run simulations in parallel, using half the available cores
    n_cores = MP.cpu_count()//2
    with MP.get_context("spawn").Pool(n_cores) as pool:
        outputs = pool.starmap(run_simulation, inputs)
    # Outputs is a list of tuples (just like inputs); each tuple contains the output of a simulation

    # Note: the memory will be freed only after all the outputs are collected.
    # It is then wise to split the list of inputs into smaller batches in case
    # a high number of simulations are run, to avoid overflowing the memory.