JijModeling 2.7.0 Release Notes#

Feature Enhancements#

Improved compiler speed and memory efficiency#

Major compiler optimizations have substantially improved execution speed and memory efficiency 🎉

Benchmarks show speedups of up to 8x over JijModeling 2.6.0 and up to 5x over 1.14.2. The representative execution times below are normalized to 1.0 for 2.7.0. A larger value means that the comparison version took longer than this release.

Vertical bar chart comparing relative execution time for JijModeling 1.14.2, 2.6.0, and 2.7.0 across representative Knapsack, supportcase18, and FMA workloads

Fig. 7 Relative compilation time in representative benchmarks. The labels above the bars are ratios to 2.7.0 (1.0 or higher means that 2.7.0 is as fast or faster).#

Memory allocation per compilation has also decreased substantially. Specifically, the total memory allocated per compilation decreased by 76–97% compared with 2.6.0 and by 51–94% compared with 1.14.2.

Vertical bar chart comparing total memory allocated per compilation for JijModeling 1.14.2, 2.6.0, and 2.7.0 across the same ordered Knapsack, supportcase18, and FMA workloads as the timing chart

Fig. 8 Total memory allocated per compilation in representative benchmarks#

All benchmarks were run on a Google Cloud n2-standard-8 VM (8 vCPUs, 32 GB, Ubuntu 26.04 LTS, x86_64).

Models whose compilation time is a bottleneck can benefit substantially from these improvements, so please consider migrating to JijModeling 2.

Automatically obtain parameters when using the update decorator API#

Previously, when using @Problem.update, you’d have to manually obtain already-defined objects (eg. decision variables, placeholders) by accessing problems.decision_vars and the like. Now, you can define additional parameters to the function which will be automatically obtained (by name) from that problem.

import jijmodeling as jm


@jm.Problem.define("MyProblem")
def problem(problem):
    w = problem.Float(ndim=1, description="Weights of the items")
    N = w.len_at(0)
    W = problem.Float(description="Total weight")
    x = problem.BinaryVar(shape=(N,), description="Selected items")


@problem.update
def _myupdate(
    problem: jm.DecoratedProblem,
    w: jm.Placeholder,
    W: jm.Placeholder,
    x: jm.DecisionVar,
):
    problem += problem.Constraint("weight", jm.sum(w * x) <= W)

    # as before, you can still define new variables and placeholders:
    v = problem.Float(ndim=1, description="Values of the items")
    problem += jm.sum(v * x)


problem
\[\begin{array}{rl} \text{Problem}\colon &\text{MyProblem}\\\displaystyle \min &\displaystyle \sum _{\vec{\imath }}{{{\left(v\cdot x\right)}}_{\vec{\imath }}}\\&\\\text{s.t.}&\\&\begin{aligned} \text{weight}&\quad \displaystyle \sum _{\vec{\imath }}{{{\left(w\cdot x\right)}}_{\vec{\imath }}}\leq W\end{aligned} \\&\\\text{where}&\\&\text{Decision Variables:}\\&\qquad \begin{alignedat}{2}{x}_{i}&\in \left\{0,1\right\}&\qquad &\text{a }1\text{-dim array of }\text{binary}\text{ decision variables}\\&\forall i\in \left\{0,\ldots ,\mathop{\mathtt{len\_{}at}}\left(w,0\right)-1\right\}&&\text{Selected items}\\\end{alignedat}\\&\\&\text{Placeholders:}\\&\qquad \begin{alignedat}{2}{v}_{i}&\in \mathbb{R}&\qquad &\text{a }1\text{-dim array of placeholders with elements in }\mathbb{R}\\&\forall i\in \left\{0,\ldots ,\mathop{\mathtt{len\_{}at}}\left(v,0\right)-1\right\}&&\text{Values of the items}\\&&&\\W&\in \mathbb{R}&\qquad &\text{a scalar placeholder in }\mathbb{R}\\&&&\text{Total weight}\\&&&\\{w}_{i}&\in \mathbb{R}&\qquad &\text{a }1\text{-dim array of placeholders with elements in }\mathbb{R}\\&\forall i\in \left\{0,\ldots ,\mathop{\mathtt{len\_{}at}}\left(w,0\right)-1\right\}&&\text{Weights of the items}\\\end{alignedat}\end{array} \]

Improvements to the Type Mismatch error#

The Type Mismatch error now includes the term whose type actually mismatched when needed.

import jijmodeling as jm


try:

    @jm.Problem.define("MyProblem")
    def problem(problem: jm.DecoratedProblem):
        N = problem.Length()
        W = problem.Float()
        x = problem.BinaryVar(shape=N)

        problem += x[W]  # Error!
except Exception as e:
    print(e)
Traceback (most recent last):
    while checking if expression `x[W]` has type `float!`,
        defined at File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_47462/2016213372.py", line 12, col 20-24
    while inferring the type of expression `x[W]`,
        defined at File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_47462/2016213372.py", line 12, col 20-24
    while inferring the type of expression `x[W]`,
        defined at File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_47462/2016213372.py", line 12, col 20-24
    while checking if type `Array[N; binary!]` can be subscripted with (W): (float),
        defined at File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_47462/2016213372.py", line 12, col 20-24
    while checking if expression `W` has type `natural`,
        defined at File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_47462/2016213372.py", line 12, col 22-23

File "/var/folders/mg/mg6st30d18s7pxjjrk6pkxym0000gn/T/ipykernel_47462/2016213372.py", line 12, col 22-23:

    12  |          problem += x[W]  # Error!
                                ^

error[E-TE0004] Could not match actual type `float` with expected `natural` on an expression `W`

Hint: You can read the description and possible fix at https://jij-inc-jijmodeling.readthedocs-hosted.com/en/stable/error_codes/error/E-TE0004.html

The corresponding entry in the Error Code Index is now also more detailed.

Improved math output for subscripted variables#

Subscripted variables are now displayed in a more readable way.

import jijmodeling as jm


@jm.Problem.define("Vars Beautiful")
def problem(problem: jm.DecoratedProblem):
    C = problem.CategoryLabel()
    N = problem.Natural()
    M = problem.Natural()
    w = problem.Float(shape=(N, M))

    x = problem.ContinuousVar(
        shape=(N, M),
        lower_bound=w,
        upper_bound=2,
        description="添え字がわかりやすくなった",
    )
    z = problem.IntegerVar(
        dict_keys=(C, N),
        lower_bound=lambda c, i: i,
        upper_bound=42,
    )
    u = problem.BinaryVar()


problem
\[\begin{array}{rl} \text{Problem}\colon &\text{Vars Beautiful}\\\displaystyle \min &\displaystyle 0\\&\\\text{where}&\\&\text{Decision Variables:}\\&\qquad \begin{alignedat}{2}u&\in \left\{0,1\right\}&\qquad &\text{a binary decision variable}\\&&&\\{x}_{i,j}&\in \mathbb{R}\;\left({w}_{i,j}\leq {x}_{i,j}\leq 2\right)&\qquad &\text{a }2\text{-dim array of }\text{continuous}\text{ decision variables}\\&\forall i\in \left\{0,\ldots ,N-1\right\},\;\forall j\in \left\{0,\ldots ,M-1\right\}&&\text{添え字がわかりやすくなった}\\&&&\\{z}_{c,i}&\in \mathbb{Z}\;\left(i\leq {z}_{c,i}\leq 42\right)&\qquad &\text{a dictionary of }\text{integer}\text{ decision variables}\\&\forall c\in \mathrm{C},\;\forall i\in \left\{0,\ldots ,N-1\right\}&&\\\end{alignedat}\\&\\&\text{Placeholders:}\\&\qquad \begin{alignedat}{2}M&\in \mathbb{N}&\qquad &\text{a scalar placeholder in }\mathbb{N}\\&&&\\N&\in \mathbb{N}&\qquad &\text{a scalar placeholder in }\mathbb{N}\\&&&\\{w}_{i,j}&\in \mathbb{R}&\qquad &\text{a }2\text{-dim array of placeholders with elements in }\mathbb{R}\\&\forall i\in \left\{0,\ldots ,N-1\right\},\;\forall j\in \left\{0,\ldots ,M-1\right\}&&\\\end{alignedat}\\&\\&\text{Category Labels:}\\&\qquad \begin{array}{rl} C&\text{Category Label}\end{array} \end{array} \]

Bugfixes#

Fixed an internal error for jm.range with computed arguments#

Previously, passing a computed expression such as N - 1 as a argument of jm.range caused an internal error (E-CE0007) when the model was evaluated, showing a message that asked users to report it as a bug in JijModeling. This affected not only domain= of constraints but every place where jm.range is evaluated, such as the index set of a summation (ranges with literal or bare-placeholder arguments like jm.range(N) were unaffected).

With this fix, ranges whose arguments contain expressions now evaluate correctly.

import jijmodeling as jm


@jm.Problem.define("RangeWithComputedBounds")
def problem(problem: jm.DecoratedProblem):
    N = problem.Natural()
    x = problem.BinaryVar(shape=(N,))
    problem += jm.sum(x[i] for i in jm.range(N - 1))
    problem += problem.Constraint("fix", lambda i: x[i] == 0, domain=jm.range(N - 1))


display(problem)

problem.eval({"N": 4})
\[\begin{array}{rl} \text{Problem}\colon &\text{RangeWithComputedBounds}\\\displaystyle \min &\displaystyle \sum _{i\in \mathop{\mathtt{range}}\left(N-1\right)}{{x}_{i}}\\&\\\text{s.t.}&\\&\begin{aligned} \text{fix}&\quad \displaystyle {x}_{i}=0\quad \forall i\;\text{s.t.}\;i\in \mathop{\mathtt{range}}\left(N-1\right)\end{aligned} \\&\\\text{where}&\\&\text{Decision Variables:}\\&\qquad \begin{alignedat}{2}{x}_{i}&\in \left\{0,1\right\}&\qquad &\text{a }1\text{-dim array of }\text{binary}\text{ decision variables}\\&\forall i\in \left\{0,\ldots ,N-1\right\}&&\\\end{alignedat}\\&\\&\text{Placeholders:}\\&\qquad \begin{alignedat}{2}N&\in \mathbb{N}&\qquad &\text{a scalar placeholder in }\mathbb{N}\\\end{alignedat}\end{array} \]
Instance(raw=<builtins.Instance object at 0x989e59180>, annotations={})