11

最終更新

この質問はsetup.py、Cのように、FORTRANコードに直接アクセスするcythonモジュールをコンパイルするaを作成する方法についてです。それは解決策へのかなり長く困難な旅でしたが、完全な混乱は文脈のために以下に含まれています。

元の質問

ヒープメモリを設定してFortranコードに渡すCythonファイルである拡張子と、可能であれば再実装を避けたい由緒ある古いモジュールであるFortranファイルがあります。

ファイルはCに正常にコンパイルされ.pyxますが、cythonコンパイラ.f90は次のエラーでファイルをチョークします。

$ python setup.py build_ext --inplace
running build_ext
cythoning delaunay/__init__.pyx to delaunay/__init__.c
building 'delaunay' extension
error: unknown file type '.f90' (from 'delaunay/stripack.f90')

これが私のセットアップファイル(上半分)です:

from distutils.core import setup, Extension
from Cython.Distutils import build_ext

ext_modules = [
  Extension("delaunay",
    sources=["delaunay/__init__.pyx",
             "delaunay/stripack.f90"])
]

setup(
  cmdclass = {'build_ext': build_ext},
  ext_modules = ext_modules,
  ...
)

注:元々、Fortranファイルの場所が誤って指定されていました(ディレクトリプレフィックスなし)が、これを修正した後もまったく同じように壊れます。

私が試したこと:

私はこれを見つけて、次のようにFortranコンパイラ(つまりgfortran)の名前を渡してみました:

$ python setup.py config --fcompiler=gfortran build_ext --inplace
usage: setup.py [global_opts] cmd1 [cmd1_opts] [cmd2 [cmd2_opts] ...]
   or: setup.py --help [cmd1 cmd2 ...]
   or: setup.py --help-commands
   or: setup.py cmd --help

error: option --fcompiler not recognized

そして、それが問題だった場合に備えて、私も削除しようとしまし--inplaceた(そうではありませんでした、一番上のエラーメッセージと同じです)。

では、このFortranをコンパイルするにはどうすればよいですか?それを自分自身にハックして.o、リンクすることで逃げることはできますか?それとも、これはCythonのバグであり、distutilsを再実装したり、プリプロセッサをハックしたりする必要がありますか?

アップデート

それで、numpy.distutilsパッケージをチェックアウトしたので、私は問題をもう少し理解しました。あなたがしなければならないようです

  1. cythonを使用して.pyxファイルをcpython.cファイルに変換します。
  2. 次に、 'sのように、FortranをサポートするExtension/の組み合わせを使用します。setup()numpy

これを試した後、私のsetup.py今は次のようになります:

from numpy.distutils.core import setup
from Cython.Build import cythonize
from numpy.distutils.extension import Extension

cy_modules = cythonize('delaunay/sphere.pyx')
e = cy_modules[0]

ext_modules = [
  Extension("delaunay.sphere",
      sources=e.sources + ['delaunay/stripack.f90'])
]

setup(
  ext_modules = ext_modules,
  name="delaunay",
  ...
)

__init__.pyx(一見許可されていないように見えるので、モジュールも少し再構築したことに注意してください...)

今、物事はバグが多く、プラットフォームに依存するようになります。2つのテストシステムを利用できます。1つはMacportsPython2.7を使用するMacOSX 10.6(Snow Leopard)で、もう1つはシステムpython2.7を使用するMacOS X 10.7(Lion)です。

Snow Leopardでは、以下が適用されます。

これは、モジュールがコンパイルされることを意味します(万歳!)(--inplacenumpyはないようですが、システム全体にテストモジュールをインストールする必要がありました:/)、それでも次のようにクラッシュしimportます:

  >>> import delaunay
  Traceback (most recent call last):
    File "<input>", line 1, in <module>
    File "<snip>site-packages/delaunay/__init__.py", line 1, in <module>
      from sphere import delaunay_mesh
  ImportError: dlopen(<snip>site-packages/delaunay/sphere.so, 2): no suitable image found.  Did find:
    <snip>site-packages/delaunay/sphere.so: mach-o, but wrong architecture

そしてLionでは、かなり紛らわしい見た目のコンパイル行に続いて、コンパイルエラーが発生します。

gfortran:f77: build/src.macosx-10.7-intel-2.7/delaunay/sphere-f2pywrappers.f
/usr/local/bin/gfortran -Wall -arch i686 -arch x86_64 -Wall -undefined dynamic_lookup -bundle build/temp.macosx-10.7-intel-2.7/delaunay/sphere.o build/temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/delaunay/spheremodule.o build/temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/fortranobject.o build/temp.macosx-10.7-intel-2.7/delaunay/stripack.o build/temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/delaunay/sphere-f2pywrappers.o -lgfortran -o build/lib.macosx-10.7-intel-2.7/delaunay/sphere.so
ld: duplicate symbol _initsphere in build/temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/delaunay/spheremodule.o ldand :build /temp.macosx-10.7-intelduplicate- 2.7symbol/ delaunay/sphere.o _initsphere in forbuild architecture /i386
temp.macosx-10.7-intel-2.7/build/src.macosx-10.7-intel-2.7/delaunay/spheremodule.o and build/temp.macosx-10.7-intel-2.7/delaunay/sphere.o for architecture x86_64

ここで詳細を確認する前に、少し前に戻りましょう。まず、64ビットMacOSXのアーキテクチャの衝突については多くの頭痛の種があることを私は知っています。MacportsPythonをSnowLeopardマシンで動作させるために(システムpython 2.6からアップグレードするためだけに)非常に一生懸命働かなければなりませんでした。またgfortran -arch i686 -arch x86_64、コンパイラに混合メッセージを送信していることを確認しました。そこにはあらゆる種類のプラットフォーム固有の問題が埋め込まれているため、この質問のコンテキストで心配する必要はありません。

しかし、この行を見てみましょうgfortran:f77: build/src.macosx-10.7-intel-2.7/delaunay/sphere-f2pywrappers.f

numpyは何をしているのですか?!このビルドではf2py機能は必要ありません!私は実際にf2pyの狂気に対処することを避けるためにcythonモジュールを作成しました(4つまたは5つの出力変数と、in-in-nor-out引数が必要です-どちらもf2pyでは十分にサポートされていません)。コンパイル.c-> .o.f90->.oそしてそれらをリンクします。関連するすべてのヘッダーを含める方法を知っていれば、このコンパイラ行を自分で作成できます。

このために独自のmakefileを作成する必要はないことを教えてください...または、Fortranを(出力互換の)Cに変換する方法があるので、Pythonが.f90拡張子(全体を修正する)を見るのを避けることができます問題。)f2cこれはF77でのみ機能し、これはより新しい方言であるため、これには適していないことに注意してください(したがって.f90ファイル拡張子)。

UPDATE 2 次のbashスクリプトは、コードを適切にコンパイルしてリンクします。

PYTHON_H_LOCATION="/opt/local/Library/Frameworks/Python.framework/Versions/2.7/include/python2.7/"

cython sphere.pyx

gcc -arch x86_64 -c sphere.c -I$PYTHON_H_LOCATION
gfortran -arch x86_64 -c stripack.f90
gfortran -arch x86_64 -bundle -undefined dynamic_lookup -L/opt/local/lib *.o -o sphere.so

この種のハックをsetup.pyと互換性を持たせる方法について何かアドバイスはありますか?このモジュールをインストールしてPython.h手動で検索する必要はありません...

4

3 に答える 3

4

更新:このコンパイル行の生成を手動でまとめるプロジェクトをgithubに作成しました。それはcomplexed_buildと呼ばれます。

更新2:実際、「手動で生成する」はプラットフォーム固有であるため、非常に悪い考えです。プロジェクトは、distutils.sysconfigモジュールから値を読み取るようになりました。これは、Pythonのコンパイルに使用される設定(つまり、必要なもの)のみです。これは、Fortranコンパイラとファイル拡張子(ユーザーが構成可能)であると推測されます。私はそれが今かなりのdistutilsを再実装していると思います!


これを行う方法は、独自のコンパイラ行を作成し、それらをにハックすることsetup.pyです。以下に、次の構造を持つ私の(非常に単純な)ケースで機能する例を示します。

  • 輸入
  • cythonize()任意の.pyxファイルなので、FortranファイルとCファイルしかありません。
  • build()コードをコンパイル する関数を定義します。
    • コンパイラ名やアーキテクチャなど、変更しやすい定数かもしれません
    • fortranファイルとCファイルを一覧表示します
    • モジュールをビルドするシェルコマンドを生成します
    • リンカー行を追加します
    • シェルコマンドを実行します。
  • コマンドがinstall存在し、ターゲットがまだ存在しない場合は、それをビルドします。
  • セットアップを実行します(純粋なPythonセクションを構築します)
  • コマンドがだった場合はbuild、今すぐビルドを実行します。

これの私の実装を以下に示します。1つの拡張モジュール専用に設計されており、毎回すべてのファイルを再コンパイルするため、より一般的に使用するには、さらに拡張が必要になる場合があります。また、私はさまざまなUNIXをハードコーディングして/いるので、これをWindowsに移植する場合は、必ず適応させるか、に置き換えてos.path.sepください。

from distutils.core import setup
from distutils.sysconfig import get_python_inc
from Cython.Build import cythonize
import sys, os, shutil

cythonize('delaunay/sphere.pyx')

target = 'build/lib/delaunay/sphere.so'

def build():
  fortran_compiler = 'gfortran'
  c_compiler = 'gcc'
  architecture = 'x86_64'
  python_h_location = get_python_inc()
  build_temp = 'build/custom_temp'
  global target

  try:
    shutil.rmtree(build_temp)
  except OSError:
    pass

  os.makedirs(build_temp) # if you get an error here, please ensure the build/ ...
  # folder is writable by this user.

  c_files = ['delaunay/sphere.c']
  fortran_files = ['delaunay/stripack.f90']

  c_compile_commands = []

  for cf in c_files:
    # use the path (sans /s), without the extension, as the object file name:
    components = os.path.split(cf)
    name = components[0].replace('/', '') + '.'.join(components[1].split('.')[:-1])
    c_compile_commands.append(
      c_compiler + ' -arch ' + architecture + ' -I' + python_h_location + ' -o ' +
      build_temp + '/' + name + '.o -c ' + cf
    )

  fortran_compile_commands = []

  for ff in fortran_files:
    # prefix with f in case of name collisions with c files:
    components = os.path.split(ff)
    name = components[0].replace('/', '') + 'f' + '.'.join(components[1].split('.')[:-1])
    fortran_compile_commands.append(
      fortran_compiler + ' -arch ' + architecture + ' -o ' + build_temp + 
      '/' + name + '.o -c ' + ff
    )

  commands = c_compile_commands + fortran_compile_commands + [
    fortran_compiler + ' -arch ' + architecture + 
    ' -bundle -undefined dynamic_lookup ' + build_temp + '/*.o -o ' + target
  ]

  for c in commands:
    os.system(c)


if 'install' in sys.argv and not os.path.exists(target):
  try:
    os.makedirs('build/lib/delaunay')
  except OSError:
    # we don't care if the containing folder already exists.
    pass
  build()

setup(
  name="delaunay",
  version="0.1",
  ...
  packages=["delaunay"]
)

if 'build' in sys.argv:
  build()

Extensionこれは、独自のコマンドで、私が推測する新しいクラスにまとめることができますbuild_ext-上級生のための演習;)

于 2012-10-03T10:15:07.537 に答える
2

Pythonの外部でビンテージのFortranライブラリをビルドしてインストールし、distutilsでリンクするだけです。あなたの質問は、あなたがこのライブラリで気を緩めるつもりはないことを示しているので、(ライブラリのビルドとインストールの手順を使用して)一度限りのインストールでおそらくうまくいくでしょう。次に、Python拡張機能をインストール済みの外部ライブラリにリンクします。

ext_modules = [
    Extension("delaunay",
              sources = ["delaunay/__init__.pyx"],
              libraries = ["delaunay"])
]

このアプローチは、Matlab、Octave、IDLなどの他の言語のラッパーも必要であることに気付いた場合にも安全です。

アップデート

ある時点で、ラップしたい外部ライブラリがいくつかある場合は、これらすべてのライブラリをインストールし、すべてのラッパーのビルドも管理するトップレベルのビルドシステムを追加すると便利です。私はcmakeこの目的のために持っています。これは、システム全体のビルドとインストールを処理するのに最適です。ただし、Pythonのものをそのまま構築することはできませんが、各サブディレクトリで「python setup.py install」を呼び出して、distutilsを呼び出すように簡単に教えることができます。pythonしたがって、全体的なビルドプロセスは次のようになります。

mkdir build
cd build
cmake ..
make
make install
make python
(make octave)
(make matlab)

コアライブラリコードは、変更がかなり速い傾向があるため、特定のフロントエンド言語のラッパーから常に分離することが非常に重要です(独自のプロジェクトの場合も同様です)。それ以外の場合は、次の例で確認できますnumpy。汎用Cライブラリlibndarray.soを1つ作成し、Pythonのシンラッパーを作成する代わりに、ソースのいたるところにPythonCAPI呼び出しがあります。これはPypy、CPythonの真剣な代替手段として現在抑制されているものです。これはnumpy、CPython APIの最後のビットをすべてサポートする必要があるためです。これは、ジャストインタイムコンパイラと別のガベージコレクター。これは、多くの潜在的な改善を見逃していることを意味します。

結論:

  1. 汎用Fortran/Cライブラリを個別に構築し、システム全体にインストールします。

  2. ラッパー用に個別のビルドステップを用意します。これは、次の大きな言語Xに簡単に適応できるように、可能な限り軽量に保つ必要があります。安全な仮定が1つあるとすれば、XはCライブラリとのリンクをサポートするということです。

于 2013-05-12T09:26:27.460 に答える
0

外部でオブジェクトファイルを作成し、Extensionのコンストラクターへdistutilsの引数を使用してリンクステップでそれを含めることができます。extra_objectssetup.py

...
e = Extension(..., extra_objects = ['holycode.o'])
...

コマンドプロンプトで:

# gfortran -c -fPIC holycode.f
# ./setup.py build_ext ...

外部オブジェクトが1つしかない場合、これは多くの人にとって最も簡単な方法です。

于 2015-08-27T16:13:42.670 に答える