OpenMDAO中的并行有限差分计算执行每个过程中的每个点

时间:2019-02-01 19:16:47

标签: openmdao

我正在尝试在OpenMDAO中设置问题,并希望利用并行有限差分计算。但是,当我调用compute_totals()时,每个MPI进程实际上都会计算所有扰动点。

我做了一个最小的例子来说明这个问题。考虑可以通过矩阵乘法表示的模型的简单情况。该模型的雅可比仅仅是模型的矩阵。请参见下面的代码:

import numpy as np
import time

from openmdao.api import ExplicitComponent, Problem, IndepVarComp, Group
from openmdao.utils.mpi import MPI

rank = 0 if not MPI else MPI.COMM_WORLD.rank

class MatMultComp(ExplicitComponent):
    def __init__(self, matrix, **kwargs):
        super().__init__(**kwargs)
        self.matrix = matrix

    def setup(self):
        self.add_input('x', val=np.ones(self.matrix.shape[1])))
        self.add_output('y', val=np.ones(self.matrix.shape[0])))

    def compute(self, inputs, outputs, **kwargs):
        outputs['y'] = self.matrix.dot(inputs['x'])
        print('{} :: x = {}'.format(rank, np.array_str(inputs['x'])))


class Model(Group):
    def setup(self):
        matrix = np.arange(25, dtype=float).reshape(5, 5)
        self.add_subsystem('ivc', IndepVarComp('x', np.ones(matrix.shape[1])), promotes=['*'])
        self.add_subsystem('mat', MatMultComp(matrix), promotes=['*'])
        self.approx_totals(step=0.1)
        self.num_par_fd = matrix.shape[1]


if __name__ == '__main__':
    p = Problem()
    p.model = Model()
    p.setup()
    p.run_model()

    t0 = time.time()
    jac = p.compute_totals(of=['y'], wrt=['x'], return_format='array')
    dt = time.time() - t0

    if rank == 0:
        print('Took {:2.3f} seconds.'.format(dt))
        print('J = ')
        print(np.array_str(jac, precision=0))

当我在没有MPI的情况下运行此代码时,将得到以下输出:

0 :: x = [1. 1. 1. 1. 1.]
0 :: x = [1.1 1.  1.  1.  1. ]
0 :: x = [1.  1.1 1.  1.  1. ]
0 :: x = [1.  1.  1.1 1.  1. ]
0 :: x = [1.  1.  1.  1.1 1. ]
0 :: x = [1.  1.  1.  1.  1.1]
Took 5.008 seconds.
J = 
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]
 [10. 11. 12. 13. 14.]
 [15. 16. 17. 18. 19.]
 [20. 21. 22. 23. 24.]]

这是正确的结果,和大约需要5秒钟,如所预期。现在,当我使用MPI,使用5个进程并通过命令mpirun -np 5 python matmult.py运行此命令时,将得到以下输出:

0 :: x = [1. 1. 1. 1. 1.]
1 :: x = [1. 1. 1. 1. 1.]
2 :: x = [1. 1. 1. 1. 1.]
3 :: x = [1. 1. 1. 1. 1.]
4 :: x = [1. 1. 1. 1. 1.]
0 :: x = [1.001 1.    1.    1.    1.   ]
1 :: x = [1.001 1.    1.    1.    1.   ]
2 :: x = [1.001 1.    1.    1.    1.   ]
3 :: x = [1.001 1.    1.    1.    1.   ]
4 :: x = [1.001 1.    1.    1.    1.   ]
3 :: x = [1.    1.001 1.    1.    1.   ]
0 :: x = [1.    1.001 1.    1.    1.   ]
1 :: x = [1.    1.001 1.    1.    1.   ]
2 :: x = [1.    1.001 1.    1.    1.   ]
4 :: x = [1.    1.001 1.    1.    1.   ]
2 :: x = [1.    1.    1.001 1.    1.   ]
3 :: x = [1.    1.    1.001 1.    1.   ]
0 :: x = [1.    1.    1.001 1.    1.   ]
1 :: x = [1.    1.    1.001 1.    1.   ]
4 :: x = [1.    1.    1.001 1.    1.   ]
1 :: x = [1.    1.    1.    1.001 1.   ]
2 :: x = [1.    1.    1.    1.001 1.   ]
3 :: x = [1.    1.    1.    1.001 1.   ]
0 :: x = [1.    1.    1.    1.001 1.   ]
4 :: x = [1.    1.    1.    1.001 1.   ]
0 :: x = [1.    1.    1.    1.    1.001]
1 :: x = [1.    1.    1.    1.    1.001]
2 :: x = [1.    1.    1.    1.    1.001]
3 :: x = [1.    1.    1.    1.    1.001]
4 :: x = [1.    1.    1.    1.    1.001]
Took 5.072 seconds.
J = 
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]
 [10. 11. 12. 13. 14.]
 [15. 16. 17. 18. 19.]
 [20. 21. 22. 23. 24.]]

当然,最终结果是正确的。但是,这违背了使用MPI的目的,因为5个进程中的每个进程都计算了所有扰动点,并且像以前一样,整个执行过程大约需要5秒钟。我期望以下输出:

0 :: x = [1. 1. 1. 1. 1.]
1 :: x = [1. 1. 1. 1. 1.]
2 :: x = [1. 1. 1. 1. 1.]
3 :: x = [1. 1. 1. 1. 1.]
4 :: x = [1. 1. 1. 1. 1.]
0 :: x = [1.1 1.  1.  1.  1. ]
1 :: x = [1.  1.1 1.  1.  1. ]
2 :: x = [1.  1.  1.1 1.  1. ]
3 :: x = [1.  1.  1.  1.1 1. ]
4 :: x = [1.  1.  1.  1.  1.1]
Took 1.000 seconds.
J = 
[[ 0.  1.  2.  3.  4.]
 [ 5.  6.  7.  8.  9.]
 [10. 11. 12. 13. 14.]
 [15. 16. 17. 18. 19.]
 [20. 21. 22. 23. 24.]]

请注意,实际上,处理完成的顺序是任意的,并且所花费的时间将超过1秒。

如何使它按预期工作?请注意,我正在使用OpenMDAO 2.5.0。

1 个答案:

答案 0 :(得分:4)

这里有一些问题。首先是num_par_fd通常应作为__init__ arg传递给您的组或组件。将其设置在组件或组的setup()函数中为时已晚,因为OpenMDAO在_setup_procs函数中进行了所有MPI通信器的拆分,这发生在组件/组{ {1}}个电话。相同的计时问题适用于调用setup函数。必须在问题approx_totals调用之前调用它。最后,我们在内部用于指定并行FD计算次数的属性名称实际上是setup而不是self._num_par_fd。不建议设置内部self.num_par_fd属性,但是如果必须的话,必须在 问题_num_par_fd被调用之前设置它。

注意:这是我原始答案的经过严格编辑的版本。