pyenv+poetry環境下のnumpy,scipyでmklを使いたい

# what's this?
表題通り, pyenv+poetry環境のnumpy, scipyでmklを使おうとする話.
解決策がわかれば簡単なのだけど, そこにたどり着くまでが意外と長かった...
環境はmac OS 10.14.6 Mojave
pyenvのバージョンは1.2.16-5-g7097f820
poetryのバージョンは1.0.3
です.

# 結論(解決策)
多分, この記事を見てる人は同じような問題に当たっていち早く解決したいという人が多いでしょうから, 結論(解決策)を先に書きます.

プロジェクト名は`my-project`, 使用するpythonのバージョンは3.7.4として進めます.
`mkl`が標準通り`/opt/intel/`にインストールされていることとします.
プロジェクトのディレクトリに .venv を作るように設定されていることとします(`poetry config virtualenvs.in-project true`).

## プロジェクトディレクトリ作成 & pythonバージョン指定(ここは普通)
プロジェクトディレクトリを作成して, pythonのバージョンを指定.
ここまでは普通.

```
$ mkdir my-project && cd $_
$ pyenv local 3.7.4
```

## 仮想環境の設定(普通じゃない)
普通はpoetryが勝手に`.venv`を作ってくれるから自分で`.venv`を作る必要はないのだが, 今回は

- `.venv/bin/activate`を編集して, mklのための環境変数を設定する
- `.venv/pip.conf`を編集して, numpy,scipyをbuildするようにしたりする

ので, 自分で`.venv`を作る.

```
$ python -m venv .venv
```

mklのための環境変数を設定

```
$ echo -e "source /opt/intel/bin/compilervars.sh intel64\nsource /opt/intel/mkl/bin/mklvars.sh\nsource /opt/intel/tbb/bin/tbbvars.sh" >> .venv/bin/activate
```

`.venv/pip.conf`を作成し, `no-binary=numpy,scipy`,`no-use-pep517`を設定.

```
$ touch .venv/pip.conf && echo -e "[install]\nuse-pep517=false\nno-binary=numpy,scipy" >> .venv/pip.conf
```

numpyビルド用のコンフィグファイル`$HOME/.numpy-site.cfg`を次の内容で作成.

```
[mkl]
library_dirs = $MKLROOT/lib
include_dirs = $MKLROOT/include
mkl_libs = mkl_rt
lapack_libs =
```

## pyproject.tomlの用意(ここは普通)
準備が整ったので`poetry init`

```
$ poetry init

This command will guide you through creating your pyproject.toml config.

Package name [my-project]:  
Version [0.1.0]:  
Description []: 
Author [hogehoge, n to skip]:  
License []:  
Compatible Python versions [^3.7]:  

Would you like to define your main dependencies interactively? (yes/no) [yes] no
Would you like to define your dev dependencies (require-dev) interactively (yes/no) [yes] no
Generated file

[tool.poetry]
name = "my-project"
version = "0.1.0"
description = ""
authors = ["hogehoge"]

[tool.poetry.dependencies]
python = "^3.7"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"


Do you confirm generation? (yes/no) [yes]
```

## cythonのインストール
まずは`cython`を入れる.

```
$ poetry add cython
```

## numpyのインストール

次に`numpy`.

```
$ poetry add numpy
```

ここで, `poetry run python -c "import numpy; numpy.show_config()"`とすると,

```
Traceback (most recent call last):
(中略)
Original error was: dlopen(/path/to/my-project/.venv/lib/python3.7/site-packages/numpy/core/_multiarray_umath.cpython-37m-darwin.so, 2): Library not loaded: @rpath/libmkl_rt.dylib
  Referenced from: /path/to/my-project/.venv/lib/python3.7/site-packages/numpy/core/_multiarray_umath.cpython-37m-darwin.so
  Reason: image not found
```

と出ることがある.[^1]
[^1]: `poetry shell` して仮想環境に入ってから`python`起動して`import numpy`するとこのエラーは出ない...けど, この状態だと`poetry add scipy`が失敗するのでrpathを追加する他ない.
こうなった場合,

```
$ install_name_tool -add_rpath $MKLROOT/lib .venv/lib/python3.7/site-packages/numpy/core/_multiarray_umath.cpython-37m-darwin.so
```

を実行する.
すると,

```
$ poetry run python -c "import numpy; numpy.show_config()"
blas_mkl_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/include', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
blas_opt_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/include', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
lapack_mkl_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/include', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
lapack_opt_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/include', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
```

となって幸せの予感...

## scipyのインストール
最後にscipyを入れる.
ここまでうまくいっていればあとはすんなり行くはず.
(scipyのbuildは時間がかかります.気長に待ってください)

```
$ poetry add scipy
```

## 確認

```
poetry run python -c "import scipy; scipy.show_config()"
lapack_mkl_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/include', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
lapack_opt_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/include', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
blas_mkl_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/include', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
blas_opt_info:
    libraries = ['mkl_rt', 'pthread']
    library_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
    define_macros = [('SCIPY_MKL_H', None), ('HAVE_CBLAS', None)]
    include_dirs = ['/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/include', '/opt/intel/compilers_and_libraries_2020.0.166/mac/mkl/lib']
```

お疲れ様でした.


# 解決策に至るまで...
上の解決策に至るまでの道のりはそれなりに長かったのです.
その道のりをダイジェストでお送りします(ほぼ自分用のメモ).
読み飛ばして全く問題ないです.

## 普通にやってみた

```
$ mkdir my-project
$ pyenv local 3.7.4
$ python -m venv .venv
$ poetry init
```

`.venv/pip.conf`を

```
[install]
no-binary = numpy,scipy
```

で作成して, それ以外の`~/.numpy-site.cfg`, `.venv/bin/activate`は上の解決策と同じように編集.

`poetry add numpy`は成功する(rpath追加せなあかんけど)が, `poetry add scipy`を実行すると...

```
[EnvCommandError]
Command ['/path/to/my-project/.venv/bin/pip', 'install', '--no-deps', 'scipy==1.4.1'] errored with the following return code 1, and output: 
Collecting scipy==1.4.1
  Using cached https://files.pythonhosted.org/packages/04/ab/e2eb3e3f90b9363040a3d885ccc5c79fe20c5b8a3caa8fe3bf47ff653260/scipy-1.4.1.tar.gz
  Installing build dependencies: started
  Installing build dependencies: still running...
  Installing build dependencies: finished with status 'done'
  Getting requirements to build wheel: started
  Getting requirements to build wheel: finished with status 'done'
    Preparing wheel metadata: started
    Preparing wheel metadata: finished with status 'error'
...(中略)...
Original error was: dlopen(/private/var/folders/9x/94rd8dwd1vg49h9bt7_0kd0w0000gn/T/pip-build-env-mb4t9me7/overlay/lib/python3.7/site-packages/numpy/core/multiarray.cpython-37m-darwin.so, 2): Library not loaded: @rpath/libmkl_rt.dylib
      Referenced from: /private/var/folders/9x/94rd8dwd1vg49h9bt7_0kd0w0000gn/T/pip-build-env-mb4t9me7/overlay/lib/python3.7/site-packages/numpy/core/multiarray.cpython-37m-darwin.so
      Reason: image not found
```

ターミナルが真っ赤になる.

## numpyをbuildした後でnumpyを`no-binary`から外せばいいのでは?
`.venv/pip.conf`を

```
[install]
no-binary = numpy
```

として`poetry add numpy`でnumpyをインストール(ここでもrpathを追加しないとダメだが)した後,`.venv/pip.conf`を

```
[install]
no-binary = scipy
```

と書き直して`poetry add scipy`とすると...うまく行く.

けどなんか面倒なので...
もうちょっと探ってみる.

## pyenvだけで原因調査
端折りますが, pyenvだけでも同じ症状が出る...
`/private/var/...`でbuildしてるからダメなのでは?
pyenvだけの環境で`--no-build-isolation`をつけてpipでscipyをインストールしてみるとうまく行く.

## poetry環境で`no-build-isolation`をつけてみる.
poetryでも`build-isolation`を無効にすればええんやろうと思って,`.venv/pip.conf`を

```
[install]
build-isolation = false
no-binary = numpy,scipy
```

としてやってみると...

無理でした.
ターミナルが真っ赤です.
泣きそうです.

## `no-use-pep517`ならいけるのでは?
色々調べてたら,

> Use PEP 517 for building source distributions (use --no-use-pep517 to force legacy behaviour).

を[pip reference guide](https://pip.pypa.io/en/stable/reference/pip_install/#options)で見つける.
...これ`--no-use-pep517`ならいけるのでは?
`.venv/pip.conf`を

```
[install]
use-pep517=false
no-binary=numpy,scipy
```

としてやってみる.
うまくいった.
こうして上の解決策にたどり着く.

# まとめ
面倒なのでよほどでない限りやらんほうがええと思う.

# 参考
- ["Mac に MKL 版 numpy / scipy をインストールする". Qiita.](https://qiita.com/Ishotihadus/items/f7d82a1f3a3ca6900bf7)
- [numpy/site.cfg.example](https://github.com/numpy/numpy/blob/master/site.cfg.example)
- [pip user guide](https://pip.pypa.io/en/stable/user_guide/#configuration)