JijModeling 2.11.0 リリースノート#
機能強化#
引数に依存した返値を返す関数の扱いを適正化#
JijModeling では以前から関数の引数に依存した型を持つような関数を記述することができましたが、今回のリリースでその扱いがより体系化され、そのような関数の型も正確に (n : natural) -> Array[n; natural] などと表示されるようになりました。
これに伴い、複数のバグが修正されています。本リリースノートの「バグ修正」欄では、この変更に伴い修正されたものには (Π) というマークが付けられています。
jm.map の関数引数の型をより正確に扱うよう改善#
この変更の一環として、jm.map や jm.filter などにおける関数の引数の型がより正確に扱われるようになりました。
以下の例では、jm.map の第 1 引数の関数は旧来は自然数の型 natural を取るものとしてラフに推論されていましたが、本リリースからは N未満の自然数型として正確に扱われるようになっています。
import jijmodeling as jm
problem = jm.Problem("map argument types")
N = problem.Natural("N")
x = problem.BinaryVar("x", shape=(N,))
problem += jm.map(lambda i: x[i], N).sum()
print(problem.objective)
Expression(sum(N.map(lambda (i: N): x[i])))
これに伴い、(本来であればエラーとなるべきであるにもかかわらず)これまでのバージョンで誤って受理されていたプログラムが正しく拒否されるようになりました。 このようなプログラムの例としては、以下のようにストリーム内の位置 \(n < N\) に依存して型が変わるような配列が挙げられます。 現行の JijModeling では、このような「位置に依存して型が変わるストリーム/配列」を安全に扱う仕組みがないため、本来であればエラーになるべきです。
import jijmodeling as jm
try:
@jm.Problem.define("problem")
def problem(problem: jm.DecoratedProblem):
N = problem.Natural()
x = problem.NamedExpr(jm.map(lambda n: jm.genarray(lambda j: 42, n), N))
except Exception as e:
print(e)
Traceback (most recent last):
while inferring the type of expression `N.map(lambda n: genarray(lambda j: 42, (n)))`,
defined at File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_79184/1446842279.py", line 8, col 31-80
while inferring the type of expression `N.map(lambda n: genarray(lambda j: 42, (n)))`,
defined at File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_79184/1446842279.py", line 8, col 31-80
while inferring a common result type for `lambda n: genarray(lambda j: 42, (n))` over arguments of type `N`,
defined at File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_79184/1446842279.py", line 8, col 31-80
File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_79184/1446842279.py", line 8, col 31-80:
8 | x = problem.NamedExpr(jm.map(lambda n: jm.genarray(lambda j: 42, n), N))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
error[E-TE0026] A function of type `(n : N) -> Array[n; natural]` produces output elements whose type depends on its argument, and JijModeling cannot give them one common type.
Operations that build a collection from a function, such as `map`, `flat_map`, `genarray`, and `gendict`, need one type for all output elements (for `flat_map`, the elements of the returned collections). Where part of that type depends on the argument, JijModeling replaces it with a broader type that does not. For example, the type of numbers below the argument, or below a value computed from it, becomes the type of all natural numbers. JijModeling does not do this for array lengths or function parameter types.
Possible fix: make the array lengths and function parameter types in each output element's type independent of the argument. For an array whose length depends on the argument, build a stream instead, for example `jm.map(f, n)` instead of `jm.genarray(f, n)`, because the type of a stream has no length.
Hint: You can read the description and possible fix at https://jij-inc-jijmodeling.readthedocs-hosted.com/en/stable/error_codes/error/E-TE0026.html
このプログラムは、以前は Stream[Array[n_1234, natural]] というどこでも定義されていない変数 n_1234 を含む型に誤って推論されていましたが、今回から正しくエラーとして検出されるようになりました。
バグ修正#
バグ修正 (Π):辞書のキーに対するパターンマッチを含む制約条件が、制約検出時にエラーを引き起こす問題の修正#
辞書(PartialDict / TotalDict)のキーに対して成分ごとに束縛を行う内包表記(下記の例では for i, j in c.keys())を含む制約条件があるとき、制約検出時にE-TE0014 エラーが発生していました。このようなケースでも、本リリースからは問題なく動作するようになりました:
import jijmodeling as jm
@jm.Problem.define("problem")
def problem(problem: jm.DecoratedProblem):
N = problem.Length()
c = problem.PartialDict(dtype=float, dict_keys=(N, N))
x = problem.BinaryVar(dict_keys=c.keys())
problem += problem.Constraint("onehot", jm.sum(x[i, j] for i, j in c.keys()) == 1)
problem.eval({"N": 2, "c": {(0, 0): 1.0, (1, 1): 1.0}})
Instance(raw=<builtins.Instance object at 0x7b4db5d200>, annotations={})
バグ修正 (Π):先行する for 節で束縛した変数を genarray 内で使うと、制約検出時にエラーが発生する問題の修正#
内包表記の先行する for 節で束縛した変数を後続の jm.genarray の長さに使った時に(制約検出有効化時に)E-TE0005(未束縛変数)が発生していた問題を修正しました。
たとえば、以下の例では error[E-TE0005] Unbound variable `d_...` のようなエラーが発生していましたが、現在では問題なくコンパイルされるようになっています。
import jijmodeling as jm
@jm.Problem.define("problem")
def problem(problem: jm.DecoratedProblem):
N = problem.Natural()
ub = problem.Natural(less_than=N)
x = problem.BinaryVar(shape=(N,))
problem += problem.Constraint(
"onehot",
jm.sum(
x[t]
for n in ub
for j in n
for s, d in [(n, j)]
for t in jm.genarray(s + i for i in d)
)
== 1,
)
problem.eval({"N": 3, "ub": 1})
Instance(raw=<builtins.Instance object at 0x7b4e08f600>, annotations={})
バグ修正 (Π):文字列表現に予期せぬ __dangling_* が含まれていた問題の修正#
以下のように for 文などが複雑にネストした問題を print などで文字列表示した際に、__dangling_* という不自然な変数名が表示されてしまう問題がありましたが、本リリースからは正しい変数名が表示されるようになりました:
import jijmodeling as jm
@jm.Problem.define("problem")
def problem(problem: jm.DecoratedProblem):
N = problem.Natural()
a = problem.Placeholder(
shape=(N,), dtype=(jm.DataType.NATURAL, jm.DataType.NATURAL)
)
x = problem.BinaryVar(shape=(N,))
problem += jm.sum(x[s] for n in N for s, d in [a[n]])
print(problem)
Problem(name="problem", sense=MINIMIZE, objective=sum(stream(N.flat_map(lambda (n: natural): [a[n]].map(lambda ((s, d): Tuple[natural, natural]): (n, s, d)))).map(lambda ((n, s, d): Tuple[natural, natural, natural]): x[s])), constraints=[])
以前のリリースでは、上の出力に含まれる [a[n]] が [a[__dangling_0]] と表示されていました。
バグ修正:辞書の keys に対する gendict で生成された辞書にアクセスすると panic が発生していた問題の修正#
以下の問題のように、gendict で生成された辞書の keys にアクセスすると回復不能な panic が発生する場合がありました:
import jijmodeling as jm
@jm.Problem.define("problem")
def problem(problem: jm.DecoratedProblem):
keys = problem.Natural("keys", ndim=1)
a = problem.Natural("a", ndim=1)
d = problem.NamedExpr("d", jm.gendict(k for k in keys))
problem += jm.sum(a[i] for n in d.keys() for i in n)
problem
上のコードはこれまで以下のような回復不能なエラーを引き起こしていました:
PanicException: internal error: entered unreachable code: ElementOf[stream(keys)] must wrap a set type, but got: Array[:; natural]
本リリースから、上のようなコードも期待通り動作するようになりました。
バグ修正:上限付き自然数の総和・総積の結果型を修正#
N 未満の自然数の要素に対する sum/prod の結果型が同じ上限 N を持つ型になってしまっていた問題を修正しました。
各要素が N 未満であっても、総和や総積は N 以上になり得るため、本リリースから結果型は上限のない natural になります。
最小値・最大値は要素の上限を超えないため、min/max の結果型は引き続き N です。
import jijmodeling as jm
problem = jm.Problem("sum and prod types")
N = problem.Natural("N")
a = problem.Natural("a", shape=(3,), less_than=N)
print("a:", problem.infer(a))
print("sum:", problem.infer(a.sum()))
print("prod:", problem.infer(a.prod()))
print("min:", problem.infer(a.min()))
print("max:", problem.infer(a.max()))
a: Array[3; N]
sum: natural
prod: natural
min: N
max: N
その他の変更#
修正:API ドキュメントやエージェントスキルの記述の不備を修正しました。