diff --git a/docs/source/_src_advanced_topics/optimization_pygmo.rst b/docs/source/_src_advanced_topics/optimization_pygmo.rst index 81621523..46fdcb78 100644 --- a/docs/source/_src_advanced_topics/optimization_pygmo.rst +++ b/docs/source/_src_advanced_topics/optimization_pygmo.rst @@ -145,7 +145,7 @@ have two methods: Once the UDP class is created, we must create a PyGMO problem object by passing an instance of our class to ``pygmo.problem``. Note that an instance of the UDP class -must be passed as input to pygmo.problem() and NOT the class itself. It is also possible to use a PyGMO UDP, i.e. +must be passed as input to ``pygmo.problem()`` and NOT the class itself. It is also possible to use a PyGMO UDP, i.e. a problem that is already defined in PyGMO, but it will not be shown in this tutorial. In this example, we will use only one generation. More information about the PyGMO problem class is available `on the PyGMO website `_. @@ -251,3 +251,193 @@ despite using only 10% of the computational resources. .. [Biscani2020] Biscani et al., (2020). A parallel global multiobjective framework for optimization: pagmo. Journal of Open Source Software, 5(53), 2338, https://doi.org/10.21105/joss.02338. + + +Parallelization with PyGMO +################################ + +In this section, a short guide is given on the parallelization of tasks in Python, and specifically for application with +PyGMO. Parallelization is very useful for optimization problems, because optimizations are generally quite resource +intensive processes, and this can be curbed by applying some form of parallelity. There are two flavors of parallelity +in PyGMO: One utilizing multi-threading, presented in :ref:`Multi-threading with Batch Fitness Evaluation` and one +utilizing multi-processing, presented in :ref:`Multi-processing with Islands`. For a more general guide on +parallelization, and specifically so-called batch Fitness Evaluation (BFE), have a look at :ref:`parallelization`. + + +Multi-threading with Batch Fitness Evaluation +--------------------------------------------------- + +Multi-threading in PyGMO is used with BFE; simply evaluating some fitness function in a batch, similar to the example +explained above. For this, PyGMO has classes and methods to help setup a multi-threaded optimization. For this section, +code snippets are shown below from `the hodographic shaping MGA trajectory example +`_ and adapted +to showcase the parallel capabilities. You can either define your own User-Defined Batch Fitness Evaluator (UDBFE), +explained on the `pygmo documentation `_, or use the ``batch_fitness()`` method. +Here, the latter is explained and used as this follows more naturally from :ref:`1. Creation of the UDP class` above. A +UDBFE can be applied to any UDP -- with some constraints, making it more general and easier to apply out-of-the-box. +However, using UDBFE's does not give you any control to determine how the batch is evaluated. For this reason, +``batch_fitness()`` is used for this example. + +In a UDP, BFE can be enabled by adding a ``batch_fitness()`` method to the class, as seen below. This method receives as +input a 1D flattened array of all the design parameter vectors -- the first vector ranges from index [0, n], the second +from [n, 2n] and so on, with a design variable vector of length n. The output is constructed analogously, where the +length n is equal to the number of objectives. The ``batch_fitness()`` method is somewhat of a wrapper for the +``fitness()`` method, all it has to do is convert the input array into a list of lists, then create a pool of worker +processes that can be used. + +.. tabs:: + + .. tab:: Python + + .. toggle-header:: + :header: Required **Show/Hide** + + .. code-block:: python + + from tudatpy.kernel.trajectory_design import shape_based_thrust, transfer_trajectory + import numpy as np + from typing import List, Tuple + import pygmo as pg + import matplotlib.pyplot as plt + import multiprocessing as mp + + # Tudatpy imports + import tudatpy + from tudatpy.util import result2array + from tudatpy.kernel import constants + from tudatpy.kernel.numerical_simulation import environment_setup + + .. literalinclude:: /_src_snippets/simulation/parallelization/pygmo_batch_fitness.py + :language: python + + .. tab:: C++ + + .. literalinclude:: /_src_snippets/simulation/environment_setup/req_create_bodies.cpp + :language: cpp + +Now that we have the ``batch_fitness()`` method defined, it must be called during the optimisation, which leads us to the +next code snippet. Here, we use the ``pygmo.set_bfe()`` method of a ``pygmo.algorithm()`` object to add the batch fitness +evaluation to the optimisation. Then, by default, the UDBFE ``pygmo.default_bfe()`` is given, but if the ``batch_fitness()`` +method exists in the UDP, this will automatically be used instead of the ``pygmo.default_bfe()``. You can also use the +``b`` keyword argument for ``pygmo.island`` and ``pygmo.population`` to add a UDBFE or an instance of ``pygmo.bfe``, but +this is not considered here. + +.. tabs:: + + .. tab:: Python + + .. toggle-header:: + :header: Required **Show/Hide** + + .. code-block:: python + + from tudatpy.kernel.trajectory_design import shape_based_thrust, transfer_trajectory + import numpy as np + from typing import List, Tuple + import pygmo as pg + import matplotlib.pyplot as plt + import multiprocessing as mp + + # Tudatpy imports + import tudatpy + from tudatpy.util import result2array + from tudatpy.kernel import constants + from tudatpy.kernel.numerical_simulation import environment_setup + + .. literalinclude:: /_src_snippets/simulation/parallelization/pg_bfe_evolve.py + :language: python + + .. tab:: C++ + + .. literalinclude:: /_src_snippets/simulation/environment_setup/req_create_bodies.cpp + :language: cpp + + +To show that the batch fitness evaluation actually works well, a few tests are done with various complexities: a EJ and +EMEJ transfer with two different generation counts and population sizes each. Normally, the number of function +evaluations would be a good indication of runtime complexity, however using BFE does not change that number. CPU time +and depending on the CPU usage indirectly also clock time can give an indication of the effectiveness. It should be +noted that this is software and hardware dependent, so the results should be taken with a grain of salt. For the simple +problem (EJ) with few generations, adding the BFE actually increases the CPU time by almost 200%. As for the test with +more generations, the addition of BFE increased run-time with almost 90%. The complex problem (EMEJ) shows slightly +different behaviour; the test with few generations does improves when adding BFE by about 50%. This gain increases +significantly for the test with more generations; an 80% decrease in clock time. + +.. note:: + + These simulations are tested on macOS Ventura 13.1 with a 3.1 GHz Quad-Core Intel Core i7 processor only. Four cores + (CPU's) are used during the BFE. + + ++--------------------+-------------------------+---------------------------+---------------+----------------+-----------------+ +| Transfer Sequence | Gen count and Pop size | Batch Fitness Evaluation | CPU time [s] | CPU usage [-] | Clock time [s] | ++====================+=========================+===========================+===============+================+=================+ +| EJ | gen30pop100 | no | 17.6 | 106% | 16.7 | +| | +---------------------------+---------------+----------------+-----------------+ +| | | yes | 130.7 | 443% | 29.5 | +| +-------------------------+---------------------------+---------------+----------------+-----------------+ +| | gen300pop1000 | no | 4500 | 78% | 5770 | +| | +---------------------------+---------------+----------------+-----------------+ +| | | yes | 3000 | 405% | 735 | ++--------------------+-------------------------+---------------------------+---------------+----------------+-----------------+ +| EMEJ | gen30pop100 | no | 70.1 | 97% | 72.3 | +| | +---------------------------+---------------+----------------+-----------------+ +| | | yes | 159.2 | 428% | 37.2 | +| +-------------------------+---------------------------+---------------+----------------+-----------------+ +| | gen300pop1000 | no | 4440 | 60% | 7440 | +| | +---------------------------+---------------+----------------+-----------------+ +| | | yes | 5946 | 404% | 1470 | ++--------------------+-------------------------+---------------------------+---------------+----------------+-----------------+ + + +Multi-processing with Islands +----------------------------- + +This section presents multi-processing with PyGMO using the ``pygmo.island`` and/or ``pygmo.archipelago`` class. An +island is an object that enables asynchronous optimization of its population. An archipelago is a network that connects +multiple islands -- ``pygmo.island`` objects -- with a ``pygmo.topology`` object. Islands can exchange individuals with +one another through this topology. This topology can configure the exchange of individuals between islands in an +archipelago. By default, the topology is the ``pygmo.unconnected`` type, which has no effect, resulting in a simple +parallel evolution. + +In the code snippet below, inspired by `the hodographic shaping MGA trajectory example +`_ but +parallelized with an archipelago, a group of islands evolve in parallel. Specifically, a ``pygmo.archipelago`` object is +created that initializes a ``number_of_islands`` number of ``pygmo.island`` objects using the provided ``algo``, +``prob``, and ``pop_size`` arguments. ``pygmo.archipelago`` then has an ``evolve()`` method that in turn calls the +``evolve()`` method of all ``pygmo.island`` objects separately, and allocates a python process from a process pool to +each island. The ``wait_check()`` method makes every island wait until all islands are done executing, which is needed +for any topology to exchange individuals. + + +.. tabs:: + + .. tab:: Python + + .. toggle-header:: + :header: Required **Show/Hide** + + .. code-block:: python + + from tudatpy.kernel.trajectory_design import shape_based_thrust, transfer_trajectory + import numpy as np + from typing import List, Tuple + import pygmo as pg + import matplotlib.pyplot as plt + import multiprocessing as mp + + # Tudatpy imports + import tudatpy + from tudatpy.util import result2array + from tudatpy.kernel import constants + from tudatpy.kernel.numerical_simulation import environment_setup + + .. literalinclude:: /_src_snippets/simulation/parallelization/pg_archi.py + :language: python + + .. tab:: C++ + + .. literalinclude:: /_src_snippets/simulation/environment_setup/req_create_bodies.cpp + :language: cpp + + diff --git a/docs/source/_src_advanced_topics/parallelization.rst b/docs/source/_src_advanced_topics/parallelization.rst new file mode 100644 index 00000000..c240b0db --- /dev/null +++ b/docs/source/_src_advanced_topics/parallelization.rst @@ -0,0 +1,199 @@ +.. _`parallelization`: + +*************************** +Parallelization with Python +*************************** + +This file is an introduction to the realm of parallelization, and specifically for use with tudatpy. Tudatpy has many +applications and many can be parallelized. For parallelization specifically in combination with PyGMO, further reading +is available under :ref:`Parallelization with PyGMO`. + +.. contents:: Content of this page + :local: + + +General parallelization with Python +#################################### + +In Python, you can parallelize data processing in various ways. One possible way is to use GPU's, but this is not +discussed here. For Python CPU-based parallelization, there are generally two types: multi-processing and +multi-threading. Multi-processing is a method that initializes multiple processes. This means that different processes +are running on independent CPU's, with independent memory management. Multi-threading is a method that uses multiple +threads for a single parent process with shared memory. Child processes can be run on separate threads. There are +generally two threads per CPU, and each computer system has their own amount of CPU's with their own specs. The amount +of parallellity is therefore determined by the system you want to run on. + +It should be noted that it does not always make sense to parallelize your simulations. The initialization of parallel +tasks takes longer, so there is a break even point beyond which it is worthwhile, shown in :ref:`Multi-threading +with Batch Fitness Evaluation`. To enable parallel behavior with Python, the ``multiprocessing`` module is used. Other +alternatives exist as well that are more modern, but they are not as widely spread or as thoroughly documented. Ray, for +instance, is one of these packages, it is arguably more seemless, but it is also rather new and focused on AI +applications. + +All parallel processing should be put under ``if __name__ == "__main__" :``. This line ensures that the code is only run +if that file is the file being executed directly (so not imported, for example). This prevents an infinite loop when +creating new child processes -- or starting calculations on other threads. If this line is omitted, child processes +import the python script, which then run the same script again, thereby spawning more child processes. This results in +an infinite loop. Next, ``mp.get_context("spawn")`` is a context object that has the attributes of the multiprocessing +module. Here, the ``"spawn"`` argument refers to the method that creates a new Python process. ``"spawn"`` specifically +starts a fresh Python interpreter process -- which is default on macOS and Windows. ``"fork"`` copies a Python process +using ``os.fork()``-- which is the default on Linux. ``"forkserver"`` creates a server process; a new process is then +requested and the server uses ``"fork"`` to create it. This method can generally be left at the default value. + +A ``Pool`` object is temporarily created, which is just a collection of available processes that can be allocated to +computational tasks. The number of cores you would like to appoint to the ``Pool`` is given as an argument. +Subsequently, the ``map()`` or ``starmap`` method allows for a function to be applied to an iterable, rather than a +single argument. ``map()`` allows for a single argument to be passed to the function, ``starmap()`` allows for multiple +arguments. The inputs are all the sets of input arguments in the form of a list of tuples, which constitutes the +iterable mentioned previously. The outputs are formatted analogously, where the tuples are the various outputs rather +than the input arguments. + +.. tabs:: + + .. tab:: Python + + .. toggle-header:: + :header: Required **Show/Hide** + + .. code-block:: python + + import multiprocessing as mp + import numpy as np + + from tudatpy.kernel.numerical_simulation import environment_setup, propagation_setup + from tudatpy.kernel.interface import spice + + .. literalinclude:: /_src_snippets/simulation/parallelization/general_bfe_example.py + :language: python + + .. tab:: C++ + + .. literalinclude:: /_src_snippets/simulation/environment_setup/req_create_bodies.cpp + :language: cpp + + +.. note:: + + The memory will be freed only after all the outputs are collected. It may be wise to split the list of + inputs into smaller batches in case a high number of simulations are run, to avoid overflowing the memory. + +.. seealso:: + Other ways to specify the context or create a Pool object are also possible, more can be read on `the multiprocessing + documentation page `_. + +Batch Fitness Evaluation for Monte-Carlo analysis +################################################# + +In this section, the basic structure is presented that can allow for a simple, parallel Monte-Carlo analysis of any +problem. An astrodynamics example is used for obvious reasons: the `Kepler satellite orbit +`_. Using +this, we can change any parameter, let the Monte-Carlo simulations run in parallel, and enjoy the power. + +BFE Monte Carlo code structure +------------------------------ + +In the snippet below, the implementation can be seen. It is straightforward, and looks surprisingly similar to +:ref:`General parallelization with Python`. The ``run_simulation()`` function is shown below as ``run_dynamics()``. The +same concepts are applied, but rather than two integers being returned without further calculations, the inputs are the +Semi-major Axis and Eccentricity elements of the initial state which has a profound influence on the final results of +the orbit. + +.. tabs:: + + .. tab:: Python + + .. toggle-header:: + :header: Required **Show/Hide** + + .. code-block:: python + + # Load bfe modules + import multiprocessing as mp + + # Load standard modules + import numpy as np + from matplotlib import pyplot as plt + + # Load tudatpy modules + from tudatpy.kernel.interface import spice + from tudatpy.kernel import numerical_simulation + from tudatpy.kernel.numerical_simulation import environment_setup, propagation_setup + from tudatpy.kernel.astro import element_conversion + from tudatpy.kernel import constants + from tudatpy.util import result2array + + .. literalinclude:: /_src_snippets/simulation/parallelization/mc_bfe_run.py + :language: python + + .. tab:: C++ + + .. literalinclude:: /_src_snippets/simulation/environment_setup/req_create_bodies.cpp + :language: cpp + +The basic BFE structure can be seen above. Below the ``run_dynamics()`` function is shown, which is almost identical to +code from the `Kepler satellite orbit +`_, with the small +adjustment that the initial state definition is given by the input arguments to the function rather than defined +manually. + +.. tabs:: + + .. tab:: Python + + .. toggle-header:: + :header: Required **Show/Hide** + + .. code-block:: python + + # Load bfe modules + import multiprocessing as mp + + # Load standard modules + import numpy as np + from matplotlib import pyplot as plt + + # Load tudatpy modules + from tudatpy.kernel.interface import spice + from tudatpy.kernel import numerical_simulation + from tudatpy.kernel.numerical_simulation import environment_setup, propagation_setup + from tudatpy.kernel.astro import element_conversion + from tudatpy.kernel import constants + from tudatpy.util import result2array + + .. literalinclude:: /_src_snippets/simulation/parallelization/mc_bfe_dynamics.py + :language: python + + .. tab:: C++ + + .. literalinclude:: /_src_snippets/simulation/environment_setup/req_create_bodies.cpp + :language: cpp + + +BFE Monte Carlo results +----------------------- + +Regarding the performance of the BFE, a few results are shown in the table below. Once again, a substantial improvement +is observed when conducting Monte Carlo analyses using tudatpy. + +.. note:: + + These simulations are tested on macOS Ventura 13.1 with a 3.1 GHz Quad-Core Intel Core i7 processor only. Four cores + (CPU's) are used during the BFE. + ++-----------------------+---------------------------+---------------+----------------+--------------------+ +| Number of experiments | Batch Fitness Evaluation | CPU time [s] | CPU usage [-] | Clock time [s] | ++=======================+===========================+===============+================+====================+ +| 500 | no | 107.94 | 99% | 110.51 | +| +---------------------------+---------------+----------------+--------------------+ +| | yes | 118.07 | 381% | 32.07 | ++-----------------------+---------------------------+---------------+----------------+--------------------+ +| 2000 | no | 443.83 | 99% | 457.35 | +| +---------------------------+---------------+----------------+--------------------+ +| | yes | 475.32 | 385% | 127.11 | ++-----------------------+---------------------------+---------------+----------------+--------------------+ + +.. note:: + + Other applications are possible and may be documented in the future. If you happen to implement any yourself, feel + free to contact the developers or open a pull-request. + diff --git a/docs/source/_src_snippets/simulation/parallelization/general_bfe_example.py b/docs/source/_src_snippets/simulation/parallelization/general_bfe_example.py new file mode 100644 index 00000000..410b5a9e --- /dev/null +++ b/docs/source/_src_snippets/simulation/parallelization/general_bfe_example.py @@ -0,0 +1,21 @@ +def run_simulation(arg_1, arg_2): + # Do some tudat things... + return 1, arg_1 + arg_2 + +# Main script +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) + diff --git a/docs/source/_src_snippets/simulation/parallelization/mc_bfe_dynamics.py b/docs/source/_src_snippets/simulation/parallelization/mc_bfe_dynamics.py new file mode 100644 index 00000000..8b3b1baf --- /dev/null +++ b/docs/source/_src_snippets/simulation/parallelization/mc_bfe_dynamics.py @@ -0,0 +1,74 @@ +def run_dynamics(arg_1, arg_2, spice=spice.load_standard_kernels()): + """ + Function that creates the initial conditions, termination settings, and propagation settings, and runs + create_dynamics_simulator() function and returns the state as an array. + """ + # Set simulation start and end epochs + simulation_start_epoch = 0.0 + simulation_end_epoch = constants.JULIAN_DAY + + # Create default body settings for "Earth" + bodies_to_create = ["Earth"] + + # Create default body settings for bodies_to_create, with "Earth"/"J2000" as the global frame origin and orientation + global_frame_origin = "Earth" + global_frame_orientation = "J2000" + body_settings = environment_setup.get_default_body_settings( + bodies_to_create, global_frame_origin, global_frame_orientation) + + # Create system of bodies (in this case only Earth) + bodies = environment_setup.create_system_of_bodies(body_settings) + bodies.create_empty_body("Delfi-C3") + bodies_to_propagate = ["Delfi-C3"] + central_bodies = ["Earth"] + + # Define accelerations acting on Delfi-C3 + acceleration_settings_delfi_c3 = dict( + Earth=[propagation_setup.acceleration.point_mass_gravity()] + ) + + acceleration_settings = {"Delfi-C3": acceleration_settings_delfi_c3} + + # Create acceleration models + acceleration_models = propagation_setup.create_acceleration_models( + bodies, acceleration_settings, bodies_to_propagate, central_bodies + ) + + earth_gravitational_parameter = bodies.get("Earth").gravitational_parameter + initial_state = element_conversion.keplerian_to_cartesian_elementwise( + gravitational_parameter=earth_gravitational_parameter, + semi_major_axis=arg_1, + eccentricity=arg_2, + inclination=np.deg2rad(85.3), + argument_of_periapsis=np.deg2rad(235.7), + longitude_of_ascending_node=np.deg2rad(23.4), + true_anomaly=np.deg2rad(139.87), + ) + + # Create termination settings + termination_settings = propagation_setup.propagator.time_termination(simulation_end_epoch) + + # Create numerical integrator settings + fixed_step_size = 10.0 + integrator_settings = propagation_setup.integrator.runge_kutta_4(fixed_step_size) + + # Create propagation settings + propagator_settings = propagation_setup.propagator.translational( + central_bodies, + acceleration_models, + bodies_to_propagate, + initial_state, + simulation_start_epoch, + integrator_settings, + termination_settings + ) + + # Create simulation object and propagate the dynamics + dynamics_simulator = numerical_simulation.create_dynamics_simulator( + bodies, propagator_settings + ) + + # Extract the resulting state history and convert it to an ndarray + states = dynamics_simulator.state_history + return result2array(states) + diff --git a/docs/source/_src_snippets/simulation/parallelization/mc_bfe_run.py b/docs/source/_src_snippets/simulation/parallelization/mc_bfe_run.py new file mode 100644 index 00000000..6c488d0d --- /dev/null +++ b/docs/source/_src_snippets/simulation/parallelization/mc_bfe_run.py @@ -0,0 +1,22 @@ +if __name__ == "__main__": + + plot = False + simulation_type = 'normal' #bfe or normal + + #Monte Carlo parameters + bounds = [[7000e3, 8000e3], [0.1, 0.6]] # Semi-major Axis and Eccentricity are tested here + N = 2000 + n_cores = 4 + + # Setup inputs for MC with BFE + arg_dict = {} + for it, bound in enumerate(bounds): + arg_dict[it] = np.random.uniform(bound[0], bound[1], size=N) + + inputs = [] + for k in range(len(arg_dict[0])): + inputs.append(tuple(arg_dict[p][k] for p in range(2))) + + # Run parallel MC analysis + with mp.get_context("spawn").Pool(n_cores) as pool: + outputs = pool.starmap(run_dynamics, inputs) diff --git a/docs/source/_src_snippets/simulation/parallelization/pg_archi.py b/docs/source/_src_snippets/simulation/parallelization/pg_archi.py new file mode 100644 index 00000000..9f8d7114 --- /dev/null +++ b/docs/source/_src_snippets/simulation/parallelization/pg_archi.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + seed = 42 + pop_size = 1000 + number_of_islands = 4 + +# Create Pygmo problem + transfer_optimization_problem = MGAHodographicShapingTrajectoryOptimizationProblem( + central_body, transfer_body_order, bounds, departure_semi_major_axis, departure_eccentricity, + arrival_semi_major_axis, arrival_eccentricity) + problem = pg.problem(transfer_optimization_problem) + +# Create algorithm and define its seed + algorithm = pg.algorithm(pg.sga(gen=1)) + algorithm.set_seed(seed) + +# Create island + archi = pg.archipelago(n=number_of_islands, algo=algorithm, prob=transfer_optimization_problem, pop_size=pop_size) + + num_gen = 40 + +# Initialize lists with the best individual per generation + list_of_champion_f = [] + list_of_champion_x = [] + +# mp.freeze_support() needs to be called when using multiprocessing on windows +# mp.freeze_support() + + for i in range(num_gen): + print('Evolution: %i / %i' % (i+1, num_gen)) + + archi.evolve() # Evolve archi + archi.wait_check() # Wait until all evolution tasks in the archi finish + + # Save current champion + list_of_champion_x.append(archi.get_champion_x()) + list_of_champion_f.append(archi.get_champion_f()) + + print('Evolution finished') + diff --git a/docs/source/_src_snippets/simulation/parallelization/pg_bfe_evolve.py b/docs/source/_src_snippets/simulation/parallelization/pg_bfe_evolve.py new file mode 100644 index 00000000..8e4b6c38 --- /dev/null +++ b/docs/source/_src_snippets/simulation/parallelization/pg_bfe_evolve.py @@ -0,0 +1,39 @@ +if __name__ == "__main__": + bfe = True + + seed = 42 + pop_size = 500 + +# Create Pygmo problem + transfer_optimization_problem = MGAHodographicShapingTrajectoryOptimizationProblem( + central_body, transfer_body_order, bounds, departure_semi_major_axis, departure_eccentricity, + arrival_semi_major_axis, arrival_eccentricity) + prob= pg.problem(transfer_optimization_problem) + +# Create algorithm and define its seed + algo = pg.gaco() + if bfe: + algo.set_bfe(pg.bfe()) + algo = pg.algorithm(algo) + + bfe_pop = pg.default_bfe() if bfe else None + pop = pg.population(prob=prob, size=pop_size, seed=seed) + + num_gen = 150 + +# Initialize lists with the best individual per generation + list_of_champion_f = [pop.champion_f] + list_of_champion_x = [pop.champion_x] + +# mp.freeze_support() needs to be called when using multiprocessing on windows +# mp.freeze_support() + + for i in range(num_gen): + print(f'Evolution: {i+1} / {num_gen}', end='\r') + pop =algo.evolve(pop) + + # Save current champion + list_of_champion_x.append(pop.champion_x) + list_of_champion_f.append(pop.champion_f) + print('Evolution finished') + diff --git a/docs/source/_src_snippets/simulation/parallelization/pygmo_batch_fitness.py b/docs/source/_src_snippets/simulation/parallelization/pygmo_batch_fitness.py new file mode 100644 index 00000000..d8262541 --- /dev/null +++ b/docs/source/_src_snippets/simulation/parallelization/pygmo_batch_fitness.py @@ -0,0 +1,31 @@ +def batch_fitness(self, + design_parameter_vectors: np.ndarray) -> List[float]: + """ + Function to evaluate the fitness. A single-objective optimization is used, in which the objective is the deltaV + necessary to execute the transfer. + """ + + # Compute the final index of each type of parameters + time_of_flight_index = 3 + self.no_of_legs + incoming_velocity_index = time_of_flight_index + self.no_of_swingbys + swingby_periapsis_index = incoming_velocity_index + self.no_of_swingbys + shaping_free_coefficient_index = swingby_periapsis_index + self.total_no_shaping_free_coefficients + revolution_index = shaping_free_coefficient_index + self.no_of_legs + + len_single_dpv = revolution_index + dpvs = design_parameter_vectors.reshape(len(design_parameter_vectors)//len_single_dpv, len_single_dpv) + + inputs, fitnesses = [], [] + for dpv in dpvs: + inputs.append([list(dpv)]) + + # cpu_count = len(os.sched_getaffinity(0)) + cpu_count = mp.cpu_count() + with mp.get_context("spawn").Pool(processes=int(cpu_count-4)) as pool: + outputs = pool.map(self.fitness, inputs) + + for output in outputs: + fitnesses.append(output) + + return fitnesses + diff --git a/docs/source/index.rst b/docs/source/index.rst index 84119033..e1846252 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -270,6 +270,7 @@ to get started with them, have a look at our :hidden: _src_advanced_topics/post_processing_python + _src_advanced_topics/parallelization _src_advanced_topics/optimization_pygmo .. toctree::