<jupyter_start><jupyter_text>Running Computations in ParallelContents- [Introduction](intro)- [Single-Variable Functions](single)- [Multi-Variable Functions](multi)- [IPython Based Parallel Processing](ipython)<jupyter_code>import numpy as np
from qutip import *<jupyter_output><empty_output><jupyter_text>IntroductionOften one is interested in the output of a given function as a single-parameter is varied. For instance, we can calculate the steady-state response of our system as the driving frequency is varied.  In cases such as this, where each iteration is independent of the others, we can speedup the calculation by performing the iterations in parallel. In QuTiP, parallel computations may be performed using the `parallel_map` function or the `parfor` (parallel-for-loop) function.To use the these functions we need to define a function of one or more variables, and the range over which one of these variables are to be evaluated. For example:  Single-Variable Functions<jupyter_code>def func1(x): 
    return x, x**2, x**3

a, b, c = parfor(func1, range(10))
print(a)
print(b)
print(c)<jupyter_output>[0 1 2 3 4 5 6 7 8 9]
[ 0  1  4  9 16 25 36 49 64 81]
[  0   1   8  27  64 125 216 343 512 729]<jupyter_text>or<jupyter_code>result = parallel_map(func1, range(10))
result_array = np.array(result)
print(result_array[:, 0])  # == a
print(result_array[:, 1])  # == b
print(result_array[:, 2])  # == c<jupyter_output>[0 1 2 3 4 5 6 7 8 9]
[ 0  1  4  9 16 25 36 49 64 81]
[  0   1   8  27  64 125 216 343 512 729]<jupyter_text>Note that the return values are arranged differently for the `parallel_map` and the `parfor` functions, as illustrated below. In particular, the return value of `parallel_map` is not enforced to be NumPy arrays, which can avoid unnecessary copying if all that is needed is to iterate over the resulting list:<jupyter_code>result = parfor(func1, range(5))
print(result)

result = parallel_map(func1, range(5))
print(result)<jupyter_output>[array([0, 1, 2, 3, 4]), array([ 0,  1,  4,  9, 16]), array([ 0,  1,  8, 27, 64])]
[(0, 0, 0), (1, 1, 1), (2, 4, 8), (3, 9, 27), (4, 16, 64)]<jupyter_text>Multi-Variable FunctionsThe `parallel_map` and `parfor` functions are not limited to just numbers, but also works for a variety of outputs:<jupyter_code>def func2(x):
    return x, Qobj(x), 'a' * x

a, b, c = parfor(func2, range(5))
print(a)
print(b)
print(c)
result = parallel_map(func2, range(5))
result_array = np.array(result)
print(result_array[:, 0])  # == a
print(result_array[:, 1])  # == b
print(result_array[:, 2])  # == c<jupyter_output>[0 1 2 3 4]
[ Quantum object: dims = [[1], [1]], shape = [1, 1], type = oper, isherm = True
Qobj data =
[[ 0.]]
 Quantum object: dims = [[1], [1]], shape = [1, 1], type = oper, isherm = True
Qobj data =
[[ 1.]]
 Quantum object: dims = [[1], [1]], shape = [1, 1], type = oper, isherm = True
Qobj data =
[[ 2.]]
 Quantum object: dims = [[1], [1]], shape = [1, 1], type = oper, isherm = True
Qobj data =
[[ 3.]]
 Quantum object: dims = [[1], [1]], shape = [1, 1], type = oper, isherm = True
Qobj data =
[[ 4.]]]
['' 'a' 'aa' 'aaa' 'aaaa']<jupyter_text>One can also define functions with **multiple** input arguments and even keyword arguments. Here the `parallel_map` and `parfor` functions behaves differently:While `parallel_map` only iterate over the values `arguments`, the `parfor` function simultaneously iterates over all arguments:<jupyter_code>def sum_diff(x, y, z=0): 
    return x + y, x - y, z

parfor(sum_diff, [1, 2, 3], [4, 5, 6], z=5.0)
parallel_map(sum_diff, [1, 2, 3], task_args=(np.array([4, 5, 6]),), task_kwargs=dict(z=5.0))<jupyter_output><empty_output><jupyter_text>Note that the keyword arguments can be anything you like, but the keyword values are **not** iterated over. The keyword argument *num_cpus* is reserved as it sets the number of CPU's used by parfor. By default, this value is set to the total number of physical processors on your system. You can change this number to a lower value, however setting it higher than the number of CPU's will cause a drop in performance. In `parallel_map`, keyword arguments to the task function are specified using `task_kwargs` argument, so there is no special reserved keyword arguments. The `parallel_map` function also supports progressbar, using the keyword argument `progress_bar` which can be set to `True` or to an instance of `BaseProgressBar`. There is a function called `serial_map` that works as a non-parallel drop-in replacement for `parallel_map`, which allows easy switching between serial and parallel computation.<jupyter_code>def func(x): 
    return x
result = parallel_map(func, range(50), progress_bar=True)<jupyter_output>10.0%. Run time:   0.01s. Est. time left: 00:00:00:00
20.0%. Run time:   0.01s. Est. time left: 00:00:00:00
30.0%. Run time:   0.01s. Est. time left: 00:00:00:00
40.0%. Run time:   0.01s. Est. time left: 00:00:00:00
50.0%. Run time:   0.01s. Est. time left: 00:00:00:00
60.0%. Run time:   0.01s. Est. time left: 00:00:00:00
70.0%. Run time:   0.01s. Est. time left: 00:00:00:00
80.0%. Run time:   0.01s. Est. time left: 00:00:00:00
90.0%. Run time:   0.01s. Est. time left: 00:00:00:00
100.0%. Run time:   0.01s. Est. time left: 00:00:00:00
Total run time:   0.11s<jupyter_text>IPython Based Parallel ProcessingWhen QuTiP is used with IPython interpreter, there is an alternative parallel for-loop implementation in the QuTiP module `qutip.ipynbtools`, see `parallel_map`. The advantage of this parallel_map implementation is based on IPythons powerful framework for parallelization, so the compute processes are not confined to run on the same host as the main process, i.e. cluster computations. **Note**: In order to run the IPython `parallel_map` function, you must first turn on the IPython cluster engine.<jupyter_code>from qutip.ipynbtools import parallel_map
result = parallel_map(func, range(50), progress_bar=True)
from IPython.core.display import HTML
def css_styling():
    styles = open("../styles/guide.css", "r").read()
    return HTML(styles)
css_styling()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.12245567957977675, 'fraction_numerical': 0.057616546290216675, 'mean_word_length': 4.710402999062793, 'pattern_counts': {'":': 0, '<': 28, '<?xml version=': 0, '>': 28, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 4, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '8', 'dir': 'jupyter-structured-clean-dedup', 'id': '342373', 'license': 'MIT', 'n_tokens_mistral': 2259, 'n_tokens_neox': 2020, 'n_words': 809, 'path': 'qutip-notebooks-master/docs/guide/Parallel.ipynb', 'repo_name': 'OliverDudgeon/QSync', 'size': '6092'}