logの和の近似

\log{n!}の近似によくスターリングの公式が用いられます。
\log{n!} = n\log{n}-n
これで十分なことも多いですが、もう少しよい近似を考えてみましょう。

積分で近似して、
 \displaystyle S_1 = \int_2^{n+1}{\log{(x-\frac{1}{2}})dx}

この差を取ると、
 \displaystyle S - S_1 = \sum_{k=2}^n{(\log{k} - \int_k^{k+1}{\log{(x-\frac{1}{2}})dx})}

summationの中は、
 \log{k} - (k + \frac{1}{2})\log{(k + \frac{1}{2})} + (k - \frac{1}{2})\log{(k - \frac{1}{2})} + 1

 \log{(x+a)}テイラー展開すると、
 \log{(x+a)} = \log{x} + ax^{-1} - \frac{1}{2}a^2x^{-2} + \frac{1}{3}a^3x^{-3} + \cdots

 (x+a)\log{(x+a)}
 = (x+a)\log{x} + (x+a)(ax^{-1} - \frac{1}{2}a^2x^{-2} + \frac{1}{3}a^3x^{-3} + \cdots)
 = (x+a)\log{x} + a + a^2x^{-1} - \frac{1}{2}(a^2x^{-1}+a^3x^{-2}) + \frac{1}{3}(a^3x^{-2}+a^4x^{-3}) + \cdots
 = (x+a)\log{x} + a + \frac{1}{1\cdot 2}a^2x^{-1} - \frac{1}{2\cdot 3}a^3x^{-2} + \cdots

 (x+a)\log{(x+a)} - (x-a)\log{(x-a)}
 = 2a\log{x} + 2a - \frac{2}{2\cdot 3}a^3x^{-2} + \frac{2}{4\cdot 5}a^5x^{-4} + \cdots

だから、summationの中は、
 \frac{1}{2\cdot 3\cdot 2^2}k^{-2} + \frac{1}{4\cdot 5\cdot 2^4}k^{-4} + \cdots

 \displaystyle S - S_1 = \frac{1}{2\cdot 3\cdot 2^2}\sum_{k=2}^n{\frac{1}{k^2}} + \frac{1}{4\cdot 5\cdot 2^4}\sum_{k=2}^n{\frac{1}{k^4}} + \cdots

右辺第一項はリーマン・ゼータ関数

 \zeta(s) = \displaystyle \sum_{n=1}^{\infty}{\frac{1}{n^s}}

の特殊値

 \zeta(2) = \frac{\pi^2}{6}

が使えて、

 \displaystyle \sum_{k=2}^n{\frac{1}{k^2}} = \zeta(2) - 1 - \sum_{k=n+1}^{\infty}{\frac{1}{k^2}}

右辺第3項は上と同様な積分による近似が使えて、

 \displaystyle \sum_{k=n+1}^{\infty}{\frac{1}{k^2}} = \frac{1}{n+\frac{1}{2}} + O(n^{-3})

 S-S_1の右辺の第2項以降は、数値計算して、

from itertools import count

eps = 1e-22
v = []
for m in count(4, 2):
	a = 1.0 / (m*(m+1)*2**m)
	if a / 2**m < eps:
		break
	for k in count(2):
		b = a / k**m
		if b < eps:
			break
		v.append(b)

print(sum(sorted(v)))
0.000263942581577

 0.000263942581577 + O(n^{-3})

ということが分かりました。
以上をまとめると、

 S = (n+\frac{1}{2})\log{(n+\frac{1}{2})} - n + 1 - \frac{3}{2}\log{\frac{3}{2}} + \pi^2/144 + \frac{1}{24n+12} + 0.000263942581577 + O(n^{-3})

さらに定数項をまとめると、

 S = (n+\frac{1}{2})\log{(n+\frac{1}{2})} - n + \frac{1}{24n+12} + 0.46060519987133985 + O(n^{-3})

検証してみましょう。

from math import log

for n in (10, 20, 30):
	S = sum(log(m) for m in range(1, n+1))
	S2 = (n+0.5)*log(n+0.5)-n-1.0/(24*n+12)+0.41893853320467317
	print(n, S-S2)
>||

>||
10 2.0936225819667698e-06
20 2.8191458056880947e-07
30 8.56364152923561e-08

およそ-3乗で誤差が減っていっていることが分かると思います。

 n+\frac{1}{2}が気持ち悪かったら、そこも展開すると、

 S = n\log{n} - n + \frac{1}{2}\log{n} + \frac{1}{12n} + 0.91893853320467317 + O(n^{-3})

となりました。

新型コロナウイルスに関連した患者の死亡の統計

https://www.bousai.metro.tokyo.lg.jp/taisaku/saigai/1010035/1012790/index.html
ここから辿ると、死亡情報が例えば、

https://www.bousai.metro.tokyo.lg.jp/taisaku/saigai/1010035/1012790/1012818.html

のように得られます。年代・性別・居住地・診断日・死亡日が得られます。これに報告日をつけて、次のようなテーブルを得ました。

発表日 番号 年代 性別 居住地 診断日 死亡日
2021/1/14 1 90代 男性 都内 2020/12/9 2020/12/27
2021/1/14 2 70代 女性 都内 2020/12/27 2021/1/13
2021/1/14 3 80代 男性 都内 2020/12/31 2021/1/13

以下略

ただし、10/29以前はPDFでの情報提供のため、ここでは10/30以降の情報になっています。
これをRに読み込みます。

table <- read.csv2("death.csv", sep=",", colClasses=c("Date", NA, NA, NA, NA, "Date", "Date"))

発表日、診断日、死亡日のヒストグラムを描きます。

png("発表日.png")
hist(table[, 1], breaks="days", xlab="published day", freq=T, format="%m/%d")
dev.off()

f:id:inamori:20210116154127p:plain

png("診断日.png")
hist(table[, 6], breaks="days", xlab="diagnosis day", freq=T, format="%m/%d")
dev.off()

f:id:inamori:20210116154152p:plain

png("死亡日.png")
hist(table[, 7], breaks="days", xlab="death day", freq=T, format="%m/%d")
dev.off()

f:id:inamori:20210116154220p:plain

発表日より死亡日の方がバラツキが少ないことが分かります。
あと、死亡から発表のタイムラグと、診断から死亡までの日数が気になるので、これらもヒストグラムにします。

png("死亡から発表.png")
v <- as.integer(table[, 1] - table[, 7])
bins <- seq(min(v), max(v))
hist(v, breaks=bins, xlab="death to publish", freq=T)
dev.off()

f:id:inamori:20210116154658p:plain

png("診断から死亡.png")
v <- as.integer(table[, 7] - table[, 6])
bins <- seq(min(v), max(v))
hist(v, breaks=bins, xlab="diagnosis to death", freq=T)
dev.off()

f:id:inamori:20210116154717p:plain

診断から死亡はマイナスもけっこうあります。

PythonのライブラリをC++で作成する(5)

もう一つ、これはC/C++で行列の掛け算を書いたことがある人にはよく知られていると思いますが、乗数の方の行列を転置すると速くなります。
もう一度C++のコードを見てみましょう。

    Matrix  C(H, Vec(W));
    for(size_t i = 0U; i < H; ++i) {
        for(size_t j = 0U; j < W; ++j) {
            __int128    e = 0;
            for(size_t k = 0U; k < M; ++k)
                e += A[i][k] * B[k][j];
            C[i][j] = (long)(e % D);
        }
    }

一番内側のループでkを振っているので、行列Bを縦にスキャンしていくことになります。しかし、メモリは横に並んでいるので、飛び飛びに値を拾うことになり、キャッシュの中身が次々と入れ替わることになります。Bを転置すると、

    const Matrix    B_trans = transpose(B);
    Matrix  C(H, Vec(W));
    for(size_t i = 0U; i < H; ++i) {
        for(size_t j = 0U; j < W; ++j) {
            __int128    e = 0;
            for(size_t k = 0U; k < M; ++k)
                e += A[i][k] * B_trans[j][k];
            C[i][j] = (long)(e % D);
        }
    }

B_transは横にスキャンしています。これでキャッシュの入れ替わりが少なくなり、高速化されます。

実行してみましょう。

1134
256330968
12.208092451095581

これまでのバージョンの時間を並べてみましょう。

method time ratio
Python 1756.86s 225.8
PyPy 277.08s 22.7
C++ 206.85s 16.9
C++128 30.57s 2.5
C++transpose 12.21s 1

最後に、コードを表示します。

from distutils.core import setup, Extension

setup(name = 'mymatrix', version = '1.0.0', \
    ext_modules = [Extension('mymatrix', ['matrix.cpp'])])
// matrix.h
#include <Python.h>
#include <vector>

typedef std::vector<long>   Vec;
typedef std::vector<Vec>    Matrix;

static Vec make_vector(PyObject *obj_list);
static Matrix make_matrix(PyObject *args);
static PyObject *mul(PyObject *self, PyObject *args);
static Matrix mul_core(const Matrix& A, const Matrix& B, long D);
static Matrix mul_core128(const Matrix& A, const Matrix& B, long D);
static Matrix mul_core_trans(const Matrix& A, const Matrix& B, long D);
static Matrix transpose(const Matrix& A);
static PyObject *make_python_matrix(const Matrix& A);
static PyObject *make_python_vector(const Vec& v);
// matrix.cpp
#include <iostream>
#include "matrix.h"

using namespace std;

Vec make_vector(PyObject *obj_list) {
    if(!PyList_Check(obj_list))
        return Vec(0);
    
    Vec v;
    const int   M = PyList_Size(obj_list);
    for(int j = 0; j < M; ++j) {
        PyObject    *obj = PyList_GetItem(obj_list, (Py_ssize_t)j);
        if(!PyLong_Check(obj)) {
            printf("vector[%d] is not long.\n", j);
            return Vec(0);
        }
        
        const long  d = PyLong_AsLong(obj);
        v.push_back(d);
    }
    
    return v;
}

Matrix make_matrix(PyObject *obj_table) {
    if(!PyList_Check(obj_table))
        return Matrix(0);
    
    const int   L = PyList_Size(obj_table);
    Matrix  X(L);
    for(int i = 0; i < L; ++i) {
        PyObject    *obj = PyList_GetItem(obj_table, (Py_ssize_t)i);
        Vec v = make_vector(obj);
        if(v.empty())
            return Matrix(0);
        X[i] = v;
    }
    
    return X;
}

PyObject *mul(PyObject *self, PyObject *args) {
    PyObject    *obj1, *obj2;
    long    D;
    if(!PyArg_ParseTuple(args, "OOl", &obj1, &obj2, &D))
        return make_python_matrix(Matrix(0));
    
    const Matrix    E = make_matrix(obj2);
    const Matrix    A = make_matrix(obj1);
    const Matrix    B = make_matrix(obj2);
    Matrix  C = mul_core_trans(A, B, D);
    return make_python_matrix(C);
}

Matrix mul_core(const Matrix& A, const Matrix& B, long D) {
    const size_t    H = A.size();
    const size_t    M = B.size();
    const size_t    W = B.front().size();
    
    Matrix  C(H, Vec(W));
    for(size_t i = 0U; i < H; ++i) {
        for(size_t j = 0U; j < W; ++j) {
            for(size_t k = 0U; k < M; ++k)
                C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % D;
        }
    }
    return C;
}

Matrix mul_core128(const Matrix& A, const Matrix& B, long D) {
    const size_t    H = A.size();
    const size_t    M = B.size();
    const size_t    W = B.front().size();
    
    Matrix  C(H, Vec(W));
    for(size_t i = 0U; i < H; ++i) {
        for(size_t j = 0U; j < W; ++j) {
            __int128    e = 0;
            for(size_t k = 0U; k < M; ++k)
                e += A[i][k] * B[k][j];
            C[i][j] = (long)(e % D);
        }
    }
    return C;
}

Matrix mul_core_trans(const Matrix& A, const Matrix& B, long D) {
    const size_t    H = A.size();
    const size_t    M = B.size();
    const size_t    W = B.front().size();
    
    const Matrix    B_trans = transpose(B);
    Matrix  C(H, Vec(W));
    for(size_t i = 0U; i < H; ++i) {
        for(size_t j = 0U; j < W; ++j) {
            __int128    e = 0;
            for(size_t k = 0U; k < M; ++k)
                e += A[i][k] * B_trans[j][k];
            C[i][j] = (long)(e % D);
        }
    }
    return C;
}

Matrix transpose(const Matrix& A) {
    const size_t    H = A.size();
    const size_t    W = A.front().size();
    Matrix  B(W, vector<long>(H));
    for(size_t i = 0U; i < W; ++i) {
        for(size_t j = 0U; j < H; ++j)
            B[i][j] = A[j][i];
    }
    return B;
}

PyObject *make_python_matrix(const Matrix& A) {
    const size_t    H = A.size();
    PyObject    *obj_mat = PyList_New(H);
    for(size_t i = 0U; i < H; ++i) {
        PyObject    *obj_list = make_python_vector(A[i]);
        PyList_SET_ITEM(obj_mat, i, obj_list);
    }
    return obj_mat;
}

PyObject *make_python_vector(const Vec& v) {
    const size_t    L = v.size();
    PyObject    *obj_list = PyList_New(L);
    for(size_t i = 0U; i < L; ++i)
        PyList_SET_ITEM(obj_list, i, PyLong_FromLong(v[i]));
    return obj_list;
}

static PyObject *print_matrix(PyObject *self, PyObject *args) {
    PyObject    *obj_table;
    if(!PyArg_ParseTuple(args, "O", &obj_table))
        return make_python_matrix(Matrix(0));
    
    Matrix  X = make_matrix(obj_table);
    for(auto p = X.begin(); p != X.end(); ++p) {
        for(auto q = p->begin(); q != p->end(); ++q)
            cout << *q << ' ';
        cout << endl;
    }
    
    return PyLong_FromLong(0);
}

static PyMethodDef mymatrixMethods[] = {
    { "print_matrix", print_matrix, METH_VARARGS },
    { "mul", mul, METH_VARARGS },
    { NULL }
};

static struct PyModuleDef mymatrix = {
    PyModuleDef_HEAD_INIT, "mymatrix", "Python3 C API Module",
    -1, mymatrixMethods
};

PyMODINIT_FUNC PyInit_mymatrix() {
    return PyModule_Create(&mymatrix);
}
# panel.py
# coding: utf-8
from itertools import *
from collections import Counter
import sys
import time
import mymatrix


#################### Matrix ####################

def mat_pow(M, e, D):
    if e == 1:
        return M
    elif e % 2 == 1:
        return mymatrix.mul(M, mat_pow(M, e-1, D), D)
    else:
        A = mat_pow(M, e//2, D)
        return mymatrix.mul(A, A, D)


#################### process ####################

def f(H, W, D):
    def normalize(diagram):
        min_l = min(diagram)
        return tuple(row - min_l for row in diagram)
    
    def nexts(diagram):
        y = next(y for y, row in enumerate(diagram) if row == 0)
        yield diagram[:y] + (2,) + diagram[y+1:]
        if y+1 < H and diagram[y+1] == 0:
            yield diagram[:y] + (1, 1) + diagram[y+2:]
    
    def walk(diagram):
        for diagram1 in nexts(diagram):
            diagram2 = normalize(diagram1)
            if diagram2 not in set_diagram:
                set_diagram.add(diagram2)
                walk(diagram2)
    
    def make_graph(H):
        graph = Counter()       # { (orig, dest): counter }
        for diagram in set_diagram:
            for diagram1 in nexts(diagram):
                graph[(diagram, normalize(diagram1))] += 1
        return graph
    
    def make_matrix(H):
        graph = make_graph(H)
        M = [ [ graph[(orig, dest)] for orig in set_diagram ]
                                    for dest in set_diagram ]
        row_to_index = dict((diagram, i) for i, diagram in enumerate(set_diagram))
        return (M, row_to_index)
    
    init_diagram = (0,)*H
    set_diagram = set([init_diagram])
    walk(init_diagram)
    M, row_to_index = make_matrix(H)
    print(len(M))
    M_pow = mat_pow(M, W*H//2, D)
    i = row_to_index[init_diagram]
    return M_pow[i][i]


#################### main ####################

t0 = time.time()
D = 10**9+7
H = int(sys.argv[1])
W = int(sys.argv[2])
print(f(H, W, D))
print(time.time() - t0)

PythonのライブラリをC++で作成する(4)

なぜC++で書いたライブラリは遅いのでしょう。
Pythonのコードは、

    for i in range(H):
        for j in range(W):
            C[i][j] = int(sum(A[i][k] * B[k][j] for k in range(M)) % D)
    return C

C++のコードは、

    for(size_t i = 0U; i < H; ++i) {
        for(size_t j = 0U; j < W; ++j) {
            for(size_t k = 0U; k < M; ++k)
                C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % D;
        }
    }

Pythonのコードは総和を取ったあとにDの剰余を取っていますが、C++のコードは掛け算して足し算するたびにDの剰余を取っています。
なぜこうなっているかというと、競技プログラミングではDが32ビットに近い数が選ばれるため、すぐに64ビットではオーバーフローしてしまうからです。一方、Pythonでは多倍長整数に移行するため、オーバーフローを考慮しなくてよく、また多倍長整数の加算は64ビット整数の除算よりずっと速いです。
C++では64ビットより長い整数を使えばよいですが、幸いにしてGCCには__int128という128ビット整数があり、これを利用すれば速くなります。

PyObject *mul(PyObject *self, PyObject *args) {
    ...
    Matrix  C = mul_core128(A, B, D);
    return make_python_matrix(C);
}

Matrix mul_core128(const Matrix& A, const Matrix& B, long D) {
    const size_t    H = A.size();
    const size_t    M = B.size();
    const size_t    W = B.front().size();
    
    Matrix  C(H, Vec(W));
    for(size_t i = 0U; i < H; ++i) {
        for(size_t j = 0U; j < W; ++j) {
            __int128    e = 0;
            for(size_t k = 0U; k < M; ++k)
                e += A[i][k] * B[k][j];
            C[i][j] = (long)(e % D);
        }
    }
    return C;
}

実行してみましょう。

1134
256330968
30.565128803253174

変更前に対して6.8倍速くなりました。
速度アップの方法がもう一つあります。

PythonのライブラリをC++で作成する(3)

いよいよ本題、C++でMatrixの掛け算をするPythonのライブラリを作ります。

まず、リストをstd::vectorに変換します。

PyObject *mul(PyObject *self, PyObject *args) {
    PyObject    *obj1, *obj2;
    long    D;
    if(!PyArg_ParseTuple(args, "OOl", &obj1, &obj2, &D))
        ...

まず、このように引数のタプルからPyObjectを取り出します。このobj1、obj2がリストになっています。そして、このリストから要素を取り出します。以下は、リストの要素が整数の場合です。

    const int   M = PyList_Size(obj_list);
    for(int j = 0; j < M; ++j) {
        PyObject    *obj = PyList_GetItem(obj_list, (Py_ssize_t)j);
        const long  d = PyLong_AsLong(obj);
        v.push_back(d);     // std::vector<long>
    }

タプルの取り出し方とだいたい同じですね。
Matrixはこれを2層で行えばいいだけです。簡単ですね。

次に、std::vectorPythonのリストに変換します。

    const size_t    L = v.size();   // v : std::vector<long>
    PyObject    *obj_list = PyList_New(L);
    for(size_t i = 0U; i < L; ++i)
        PyList_SET_ITEM(obj_list, i, PyLong_FromLong(v[i]));
    return obj_list;

長さLのリストオブジェクトを作り、longをPyObjectに変換して、リストにセットします。

リストオブジェクト - Python 3.9.1 ドキュメント

コードは、

# setup.py
from distutils.core import setup, Extension

setup(name = 'mymatrix', version = '1.0.0', \
    ext_modules = [Extension('mymatrix', ['matrix.cpp'])])
// matrix.h
#include <Python.h>
#include <vector>

typedef std::vector<long>   Vec;
typedef std::vector<Vec>    Matrix;

static Vec make_vector(PyObject *obj_list);
static Matrix make_matrix(PyObject *args);
static PyObject *mul(PyObject *self, PyObject *args);
static Matrix mul_core(const Matrix& A, const Matrix& B, long D);
static Matrix transpose(const Matrix& A);
static PyObject *make_python_matrix(const Matrix& A);
static PyObject *make_python_vector(const Vec& v);
// matrix.cpp
#include <iostream>
#include "matrix.h"

using namespace std;

Vec make_vector(PyObject *obj_list) {
    if(!PyList_Check(obj_list))
        return Vec(0);
    
    Vec v;
    const int   M = PyList_Size(obj_list);
    for(int j = 0; j < M; ++j) {
        PyObject    *obj = PyList_GetItem(obj_list, (Py_ssize_t)j);
        if(!PyLong_Check(obj)) {
            printf("vector[%d] is not long.\n", j);
            return Vec(0);
        }
        
        const long  d = PyLong_AsLong(obj);
        v.push_back(d);
    }
    
    return v;
}

Matrix make_matrix(PyObject *obj_table) {
    if(!PyList_Check(obj_table))
        return Matrix(0);
    
    const int   L = PyList_Size(obj_table);
    Matrix  X(L);
    for(int i = 0; i < L; ++i) {
        PyObject    *obj = PyList_GetItem(obj_table, (Py_ssize_t)i);
        Vec v = make_vector(obj);
        if(v.empty())
            return Matrix(0);
        X[i] = v;
    }
    
    return X;
}

PyObject *mul(PyObject *self, PyObject *args) {
    PyObject    *obj1, *obj2;
    long    D;
    if(!PyArg_ParseTuple(args, "OOl", &obj1, &obj2, &D))
        return make_python_matrix(Matrix(0));
    
    const Matrix    A = make_matrix(obj1);
    const Matrix    B = make_matrix(obj2);
    Matrix  C = mul_core(A, B, D);
    return make_python_matrix(C);
}

Matrix mul_core(const Matrix& A, const Matrix& B, long D) {
    const size_t    H = A.size();
    const size_t    M = B.size();
    const size_t    W = B.front().size();
    
    Matrix  C(H, Vec(W));
    for(size_t i = 0U; i < H; ++i) {
        for(size_t j = 0U; j < W; ++j) {
            for(size_t k = 0U; k < M; ++k)
                C[i][j] = (C[i][j] + A[i][k] * B[k][j]) % D;
        }
    }
    return C;
}

PyObject *make_python_matrix(const Matrix& A) {
    const size_t    H = A.size();
    PyObject    *obj_mat = PyList_New(H);
    for(size_t i = 0U; i < H; ++i) {
        PyObject    *obj_list = make_python_vector(A[i]);
        PyList_SET_ITEM(obj_mat, i, obj_list);
    }
    return obj_mat;
}

PyObject *make_python_vector(const Vec& v) {
    const size_t    L = v.size();
    PyObject    *obj_list = PyList_New(L);
    for(size_t i = 0U; i < L; ++i)
        PyList_SET_ITEM(obj_list, i, PyLong_FromLong(v[i]));
    return obj_list;
}

static PyObject *print_matrix(PyObject *self, PyObject *args) {
    PyObject    *obj_table;
    if(!PyArg_ParseTuple(args, "O", &obj_table))
        return make_python_matrix(Matrix(0));
    
    Matrix  X = make_matrix(obj_table);
    for(auto p = X.begin(); p != X.end(); ++p) {
        for(auto q = p->begin(); q != p->end(); ++q)
            cout << *q << ' ';
        cout << endl;
    }
    
    return PyLong_FromLong(0);
}

static PyMethodDef mymatrixMethods[] = {
    { "print_matrix", print_matrix, METH_VARARGS },
    { "mul", mul, METH_VARARGS },
    { NULL }
};

static struct PyModuleDef mymatrix = {
    PyModuleDef_HEAD_INIT, "mymatrix", "Python3 C API Module",
    -1, mymatrixMethods
};

PyMODINIT_FUNC PyInit_mymatrix() {
    return PyModule_Create(&mymatrix);
}

Python側は、

import mymatrix

def mat_pow(M, e, D):
    if e == 1:
        return M
    elif e % 2 == 1:
        return mymatrix.mul(M, mat_pow(M, e-1, D), D)
    else:
        A = mat_pow(M, e/2, D)
        return mymatrix.mul(A, A, D)

ここが最初のコードと違うだけです。

実行してみましょう。

1134
256330968
206.85379028320312

そんなに変わりませんね。なぜでしょう。工夫する余地が2つあります。

PythonのライブラリをC++で作成する(2)

C++で簡単なPythonのライブラリを作ります。
整数同士を掛け算する関数です。

さっそくコードを示します。

# setup.py
from distutils.core import setup, Extension

setup(name = 'mymath', version = '1.0.0', \
    ext_modules = [Extension('mymath', ['mymath.cpp'])])
// mymath.cpp
#include <Python.h>

static PyObject *mul(PyObject *self, PyObject *args) {
    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b))
        return NULL;
    
    const int   c = a * b;
    return Py_BuildValue("i", c);
}

static PyMethodDef mymathMethods[] = {
    { "mul", mul, METH_VARARGS },
    { NULL }
};

static struct PyModuleDef mymath = {
    PyModuleDef_HEAD_INIT, "mymath", "Python3 C API Module",
    -1, mymathMethods
};

PyMODINIT_FUNC PyInit_mymath() {
    return PyModule_Create(&mymath);
}

ポイントは型変換です。
Pythonからやってくる値はPyObjectで、返す値もPyObjectなので、PyObjectをintに変換して、計算した結果のintをPyObjectに変換します。

まず、PyObjectをintに変換する部分です。

    int a, b;
    if (!PyArg_ParseTuple(args, "ii", &a, &b))
        return NULL;

タプルなので、PyObjectを一つずつ取り出して、それをintにしてもよいです。

    PyObject    *obj1 = PyTuple_GetItem(args, 0);
    PyObject    *obj2 = PyTuple_GetItem(args, 1);
    if(!PyLong_Check(obj1))
        return Py_None;
    if(!PyLong_Check(obj2))
        return Py_None;
    const int   a = PyLong_AsLong(obj1);
    const int   b = PyLong_AsLong(obj2);

タプルオブジェクト (tuple object) - Python 3.9.1 ドキュメント
整数型オブジェクト (integer object) - Python 3.9.1 ドキュメント

上のように、PyArg_ParseTupleを使うと一度にintを2つ取り出せます。
PyArg_ParseTupleの第2引数がどの型に変換するかを指定しています。"ii"だと2つともintに変換します。

引数の解釈と値の構築 - Python 3.9.1 ドキュメント

実行してみましょう。

$ python
Python 3.7.4 (default, Aug 13 2019, 20:35:49)
[GCC 7.3.0] :: Anaconda, Inc. on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import mymath
>>> mymath.mul(2, 3)
6

次の例だと、

>>> mymath.mul(100000, 100000)
1410065408

32ビット符号付き整数なので、ちゃんとオーバーフローしていますね。

>>> (100000*100000+2**31)%2**32-2**31
1410065408

あってます。

PythonのライブラリをC++で作成する(1)

PythonのライブラリをC++で作ります。
例えば、次のような問題を考えます。

H×Wの長方形があり、そこに1×2の長方形を隙間なく敷き詰めます。敷き詰め方は何通りあるでしょう。

2×3なら、

f:id:inamori:20210106195416p:plain

の2通りです。
さて、この問題はどのように解くでしょう。この手の問題は、状態遷移を考えます。左から順に詰めていきます。そして、縦に見て最も左が空いている場所に長方形を埋めます。最も左が空いている場所が複数あれば、上から詰めます。空の状態から一つ長方形を詰めた状態は次の2つのみです。

f:id:inamori:20210106200031p:plain

この時、全て埋まっている列は消した状態を考えます。H = 3で考えて、例えば、

f:id:inamori:20210106201615p:plain

のようにします。これを(2, 0, 1)、もしくはもう少し簡単に201と表します。000から始まって、HW/2個長方形を敷いて000になっていたらH×Wが隙間なく敷き詰められたことになります。

状態遷移は、

000 -> 200, 110
200 -> 220, 100
220 -> 000
110 -> 001
100 -> 120, 000
001 -> 201, 000
120 -> 011
201 -> 110
011 -> 100

となり、これを行列にして、この行列のW/2乗を計算すればいいわけです。

コードは次のようになりました。

# coding: utf-8
from collections import Counter
import sys
import time


#################### Matrix ####################

def mat_mul(A, B, D):
    H, M, W = len(A), len(B), len(B[0])
    C = [ [0] * W for _ in range(H) ]
    for i in range(H):
        for j in range(W):
            C[i][j] = int(sum(A[i][k] * B[k][j] for k in range(M)) % D)
    return C

def mat_pow(M, e, D):
    if e == 1:
        return M
    elif e % 2 == 1:
        return mat_mul(M, mat_pow(M, e-1, D), D)
    else:
        A = mat_pow(M, e/2, D)
        return mat_mul(A, A, D)


#################### process ####################

def f(H, W, D):
    def normalize(diagram):
        min_l = min(diagram)
        return tuple(row - min_l for row in diagram)
    
    def nexts(diagram):
        y = next(y for y, row in enumerate(diagram) if row == 0)
        yield diagram[:y] + (2,) + diagram[y+1:]
        if y+1 < H and diagram[y+1] == 0:
            yield diagram[:y] + (1, 1) + diagram[y+2:]
    
    def walk(diagram):
        for diagram1 in nexts(diagram):
            diagram2 = normalize(diagram1)
            if diagram2 not in set_diagram:
                set_diagram.add(diagram2)
                walk(diagram2)
    
    def make_graph(H):
        graph = Counter()       # { (orig, dest): counter }
        for diagram in set_diagram:
            for diagram1 in nexts(diagram):
                graph[(diagram, normalize(diagram1))] += 1
        return graph
    
    def make_matrix(H):
        graph = make_graph(H)
        M = [ [ graph[(orig, dest)] for orig in set_diagram ]
                                    for dest in set_diagram ]
        row_to_index = dict((diagram, i) for i, diagram in enumerate(set_diagram))
        return (M, row_to_index)
    
    init_diagram = (0,)*H
    set_diagram = set([init_diagram])
    walk(init_diagram)
    print(len(set_diagram))
    M, row_to_index = make_matrix(H)
    M_pow = mat_pow(M, W*H/2, D)
    i = row_to_index[init_diagram]
    return M_pow[i][i]


#################### main ####################

t0 = time.time()
D = 10**9+7
H = int(sys.argv[1])
W = int(sys.argv[2])
print(f(H, W, D))
print(time.time() - t0)

これを9×100, D = 10**9 + 7で計算してみましょう。

1134
256330968
1756.864093542099

最初の1134は、行列の大きさです。約30分かかっていますね。競技プログラミングでは、こんなに行列が大きくなることはふつうないです。
さて、計算時間はこの行列の掛け算がほとんど全てです。この手のリストを使う計算は、PyPyにすると速くなります。

1134
256330968
277.08021235466003

6.3倍速くなりました。
Project Eulerでは、PyPyは事実上の標準です。PyPyを使いましょう。
もっと速くするために、C++によるライブラリを作ります。