JijModeling 2.8.0 リリースノート#

機能強化#

SetStream に改名#

これまでの JijModeling では「走査できる値の列」を表す型は Set と呼ばれていましたが、数学的には "Set"(集合)とは重複も順番も持たないものの集まりであるため、誤解の元となっていました。

今回のリリースより、これまで Set と呼ばれていたものは Stream と呼ばれるようになり、非推奨となった jm.set 関数のかわりに jm.stream 関数が導入されました。 これは、一般のプログラミング言語ではこのような「特定の順番を持ち、重複を持った値の列」をストリームと呼ぶことにならったものです。

import jijmodeling as jm


problem = jm.Problem("stream example")
N = problem.Natural("N")
problem.infer(jm.stream(N))
\[\mathop{\mathrm{Stream}}\left[\mathbb{N}\right]\]
@problem.update
def _(problem: jm.DecoratedProblem, N: jm.Placeholder):
    # Decorator API 内では内包表記も利用可能
    print(problem.infer(jm.stream(2 * i for i in N if i % 2 == 0)))
Stream[natural]

jm.setjm.stream の別名として引き続き利用できますが、廃止予定であるため jm.stream への速やかな移行を推奨します。

gendict の LaTeX 出力の改善#

gendict関数の \(\LaTeX\) 出力の体裁を genarray と合わせました。

import jijmodeling as jm


problem = jm.Problem("gendict example")
K = problem.CategoryLabel("K")
a = problem.Float("a", dict_keys=K)
x = problem.BinaryVar("x", dict_keys=K)
Sums = problem.NamedExpr("Sums", jm.gendict(lambda k: a[k] * x[k], K))


problem
\[\begin{array}{rl} \text{Problem}\colon &\text{gendict example}\\\displaystyle \min &\displaystyle 0\\&\\\text{where}&\\&\text{Decision Variables:}\\&\qquad \begin{alignedat}{2}{x}_{i}&\in \left\{0,1\right\}&\qquad &\text{a dictionary of }\text{binary}\text{ decision variables}\\&\forall i\in K&&\\\end{alignedat}\\&\\&\text{Placeholders:}\\&\qquad \begin{alignedat}{2}{a}_{i}&\in \mathbb{R}&\qquad &\text{a dictionary of placeholders in }\mathbb{R}\\&\forall i\in \mathrm{K}&&\\\end{alignedat}\\&\\&\text{Category Labels:}\\&\qquad \begin{array}{rl} K&\text{Category Label}\end{array} \\&\\&\text{Named Expressions:}\\&\qquad \begin{alignedat}{2}Sums&={\left\{ {a}_{k}\cdot {x}_{k}\right\} }_{k\in K}&\quad &\in \mathop{\mathrm{TotalDict}}\left[K;\mathbb{R}\right]\\\end{alignedat}\end{array} \]

gendict 内包表記での if#

Decorator API の gendict 内包表記で、単一の for 節のあとに if 節を書けるようになりました。 これにより、定義域を絞り込んだ辞書を gendict 内包表記で柔軟に定義できるようになりました。

import jijmodeling as jm


@jm.Problem.define("gendict-if")
def problem(problem: jm.DecoratedProblem):
    N = problem.Length()
    c = problem.Float(dict_keys=N)
    A = problem.NamedExpr(jm.gendict(c[i] * 2 for i in N if i != 0))


problem
\[\begin{array}{rl} \text{Problem}\colon &\text{gendict-if}\\\displaystyle \min &\displaystyle 0\\&\\\text{where}&\\&\\&\text{Placeholders:}\\&\qquad \begin{alignedat}{2}{c}_{i}&\in \mathbb{R}&\qquad &\text{a dictionary of placeholders in }\mathbb{R}\\&\forall i\in \left\{0,\ldots ,N-1\right\}&&\\&&&\\N&\in \mathbb{N}&\qquad &\text{a scalar placeholder in }\mathbb{N}\\\end{alignedat}\\&\\&\text{Named Expressions:}\\&\qquad \begin{alignedat}{2}A&={\left\{ {c}_{i}\cdot 2\right\} }_{\begin{subarray}{l} i\in \left\{0,\ldots ,N-1\right\}\\i\neq 0\end{subarray} }&\quad &\in \mathop{\mathrm{TotalDict}}\left[\left\{i\mid i\in \left\{0,\ldots ,N-1\right\},i\neq 0\right\};\mathbb{R}\right]\\\end{alignedat}\end{array} \]

以下は複数の if 節を使っている例です。

import jijmodeling as jm


@jm.Problem.define("gendict-tuple-if")
def problem(problem: jm.DecoratedProblem):
    N = problem.Length()
    L = problem.CategoryLabel()
    avoid = problem.Placeholder(dtype=L)
    c = problem.Float(dict_keys=(N, L))
    OffDiag = problem.NamedExpr(
        jm.gendict(i + c[i, l] for (i, l) in (N, L) if i % 2 != 0 if l != avoid)
    )


problem
\[\begin{array}{rl} \text{Problem}\colon &\text{gendict-tuple-if}\\\displaystyle \min &\displaystyle 0\\&\\\text{where}&\\&\\&\text{Placeholders:}\\&\qquad \begin{alignedat}{2}avoid&\in \mathrm{L}&\qquad &\text{a scalar placeholder in }\mathrm{L}\\&&&\\{c}_{i,j}&\in \mathbb{R}&\qquad &\text{a dictionary of placeholders in }\mathbb{R}\\&\forall i\in \left\{0,\ldots ,N-1\right\},\;\forall j\in \mathrm{L}&&\\&&&\\N&\in \mathbb{N}&\qquad &\text{a scalar placeholder in }\mathbb{N}\\\end{alignedat}\\&\\&\text{Category Labels:}\\&\qquad \begin{array}{rl} L&\text{Category Label}\end{array} \\&\\&\text{Named Expressions:}\\&\qquad \begin{alignedat}{2}OffDiag&={\left\{ i+{c}_{i,l}\right\} }_{\begin{subarray}{l} i\in \left\{0,\ldots ,N-1\right\}\\l\in L\\i\bmod 2\neq 0\\l\neq avoid\end{subarray} }&\quad &\in \mathop{\mathrm{TotalDict}}\left[\left\{\left\langle i,l\right\rangle \mid i\in \left\{0,\ldots ,N-1\right\},l\in L,i\bmod 2\neq 0,l\neq avoid\right\};\mathbb{R}\right]\\\end{alignedat}\end{array} \]

バグ修正#

上限・下限が非有界な決定変数の存在下で制約検出がメモリを大量に消費する問題の修正#

upper_bound=float("inf") のように決定変数へ無限大の上下界を与えて非有界に指定したモデルを制約検出が有効な状態でコンパイルすると、メモリを際限なく消費することがありました。

今回のリリースではこのバグが修正され、制約検出が有効な状態でも問題なく実行されるようになりました。 また、併せて上下界に NaN を与えられたり、上界が負の無限大であるなどそもそも充足不能な場合には、定義時にエラーとなるようになりました。

import jijmodeling as jm

@jm.Problem.define("production", sense=jm.ProblemSense.MINIMIZE)
def problem(problem: jm.DecoratedProblem):
    T = problem.Length()
    demand = problem.Float(shape=(T,))
    x = problem.ContinuousVar(
        lower_bound=0.0, upper_bound=float("inf"), shape=(T,)
    )

    problem += jm.sum(x[t] for t in T)
    problem += problem.Constraint("constr", [x[t] >= demand[t] for t in T])


problem.eval({"T": 3, "demand": [1.0, 2.0, 3.0]})
Instance(raw=<builtins.Instance object at 0x60060fa80fc0>, annotations={})

辞書を map できない問題の修正#

辞書に対する map() は値を与えられた関数で移した辞書になるべきでしたが、これまでは型検査時に例外となっていました。 本リリースからは、適用前と同じキー集合を持ち、値に関数が適用された辞書を返すようになりました。

import jijmodeling as jm

problem = jm.Problem("Mapped Dicts")
N = problem.Natural("N")
L = problem.CategoryLabel("L")
x = problem.PartialDict(
    "x",
    dict_keys=(L, L),
    dtype=(L, N),
)
problem.infer(x)
\[\mathop{\mathrm{PartialDict}}\left[\mathrm{L}\times \mathrm{L};\mathrm{L}\times N\right]\]
problem.infer(x.map(lambda l, n: n))
\[\mathop{\mathrm{PartialDict}}\left[\mathrm{L}\times \mathrm{L};N\right]\]

上は PartialDict についての例ですが、TotalDict であっても同様の結果となります。