FPGA開発日記

カテゴリ別記事インデックス https://msyksphinz.github.io/github_pages , English Version https://fpgadevdiary.hatenadiary.com/

ゼロから作る量子コンピュータ・シミュレータ(4. シミュレータにPythonのインタフェースを追加する)

フルスクラッチ量子コンピュータ・シミュレータを作る話、骨格となる部分はおおよそ完成してきたので次はインタフェースの部分を調整する。

現在は量子回路を生成する部分はC++で記述しており、量子回路を書き換えるたびにリコンパイルが発生して格好悪い。

void Adder3Bit ()
{
      Qbits qbits (b << 3 | a, 10);
      FullAdder (&qbits, 0, 3, 6, 7);
      FullAdder (&qbits, 1, 4, 7, 8);
      FullAdder (&qbits, 2, 5, 8, 9);
      int res = qbits.Measure();
      res = res >> 6;
      printf ("%x\n", res);
}

そこで、Pythonのインタフェースを接続し(私の十八番)、Pythonで回路を記述するとC++にオフロードされて量子回路が作成されるようにしよう。

Python 3.6をC++と接続する

まずコンパイルの方法で戸惑った。基本的にC++側からPythonを呼び出すのは以下のように記述する。 これだけでPythonコマンドプロンプトを立ち上げることができる。

int main (int argc, char **argv)
{
  wchar_t *_program_name = Py_DecodeLocale(argv[0], NULL);
  wchar_t **_argv = nstrws_array(argc, argv);

  Py_SetProgramName(_program_name);
  Py_Initialize();

  Py_Main(argc, _argv);
  Py_Finalize ();

  return 0;
}

次に、コンパイルの方法だがかなり戸惑った。基本的には、コンパイラに渡すオプションは以下のようにして生成するのが正統派のようだ。

CFLAGS += $(shell python3-config --cflags)
LDFLAGS += $(shell python3-config --ldflags)

これはつまりpython3-config --cflagsというコマンドを実行すればコンパイル時とリンク時に必要なオプションが文字列で返されるので、それをg++に渡すのが最も簡単だということ。これを採用した。

さらに最終的なリンクの方法だが、リンクするオブジェクトより前に-lpythonを書いてはならないらしい。つまり、

$(TARGET): $(OBJS) $(HEADER)
        g++ -o $@ $^ $(LDFLAGS)

ならコンパイルできるが、

$(TARGET): $(OBJS) $(HEADER)
        g++ $(LDFLAGS) -o $@ $^

とするとエラーとなる。なんでだ!

g++ -o -Wall --std=c++14 -L/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu -L/usr/lib -lpython3.6m -lpthread -ldl  -lutil -lm  -Xlinker -export-dynamic -Wl,-O1 -Wl,-Bsymbolic-functions qbit_simulator ../src/qbit.o ../src/qbit_simulator.o ../src/util.o ../src/py_env.o ../src/qbit.hpp ../src/util.hpp ../src/py_env.hpp
/usr/bin/x86_64-linux-gnu-ld: qbit_simulator: undefined reference to symbol 'PyModule_GetState'
/usr/lib/python3.6/config-3.6m-x86_64-linux-gnu/libpython3.6m.so: error adding symbols: DSO missing from command line

C++で作ったQbitsクラスをPython用のWrapperで囲む

C++で開発したQbitsクラスは、複数量子ビットそのものを表現しており、このオブジェクトをPython上でやり取りできるようにした方がよさそうだ。 そこで、Python側ではQbitsを扱う一連の関数群をqbitsパッケージとしてまとめることにする。 このパッケージは、アプリケーションの起動時に読み込むこともできるし、パッケージとしてPython側に埋め込むこともできることにする。

int main (int argc, char **argv)
{
  wchar_t *_program_name = Py_DecodeLocale(argv[0], NULL);
  wchar_t **_argv = nstrws_array(argc, argv);

  /* Add a built-in module, before Py_Initialize */
  PyImport_AppendInittab("qbit", InitPyEnv);

  Py_SetProgramName(_program_name);
  Py_Initialize();

  /* Optionally import the module; alternatively,
     import can be deferred until the embedded script
     imports it. */
  PyImport_ImportModule("qbit");

  PyRun_SimpleString ("import qbit");
  Py_Main(argc, _argv);
  Py_Finalize ();

  return 0;
}

パッケージ内で使える関数をいくつか定義し、それをC++側でも実装していく。

PyMethodDef qbit_methods[] = {
  { "py_add",             (PyCFunction)HelloAdd,           METH_VARARGS, "Example ADD"                    },
  { "qbits" ,             (PyCFunction)MakeQbits,          METH_VARARGS, "Make Qbits"                     },
  { "get" ,               (PyCFunction)GetByIdx,           METH_VARARGS, "Get Qbits by Index"             },
  { "cnot" ,              (PyCFunction)CNot,               METH_VARARGS, "Get Applied CNot"               },
  { "ccnot" ,             (PyCFunction)CCNot,              METH_VARARGS, "Get Applied CCNot"              },
  { "val" ,               (PyCFunction)Measure,            METH_VARARGS, "Get Qbits value"                },
  { NULL,                 NULL,                            0,            NULL                             } /* Sentinel */
};

static int qbit_traverse(PyObject *m, visitproc visit, void *arg) {
  Py_VISIT(GETSTATE(m)->error);
  return 0;
}

static int qbit_clear(PyObject *m) {
  Py_CLEAR(GETSTATE(m)->error);
  return 0;
}

static struct PyModuleDef moduledef = {
  PyModuleDef_HEAD_INIT,
  "qbit",
  NULL,
  sizeof(struct module_state),
  qbit_methods,
  NULL,
  qbit_traverse,
  qbit_clear,
  NULL
};

このqbitsパッケージを生成するためのモジュールを作成する関数は以下で定義した。

PyMODINIT_FUNC InitPyEnv (void)
{
  PyObject *module = PyModule_Create (&moduledef);

  if (module == NULL) {
    return NULL;
  }

  struct module_state *st = GETSTATE(module);

  st->error = PyErr_NewException("qbit.Error", NULL, NULL);
  if (st->error == NULL) {
    Py_DECREF(module);
    return NULL;
  }

  return module;
}

ここまでで一応シミュレータ上で立ち上がったPythonプロンプトて量子ビットを作れるようになった。

次は前に作った量子加算回路をPythonで実現させたい。

f:id:msyksphinz:20180922165310p:plain