Skip to content

Commit df32a18

Browse files
authored
Merge pull request #148 from Jij-Inc/develop
Release 2.4.0
2 parents 06c4503 + eb064b8 commit df32a18

12 files changed

Lines changed: 3363 additions & 524 deletions

File tree

docs/en/_toc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ parts:
3535
title: Typical Problems
3636
- caption: Release Note
3737
chapters:
38+
- file: releases/jijmodeling-2.4.0
3839
- file: releases/jijmodeling-2.3.2
3940
- file: releases/jijmodeling-2.3.1
4041
- file: releases/jijmodeling-2.2.0

docs/en/basics/expressions.ipynb

Lines changed: 366 additions & 157 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/en/releases/jijmodeling-2.4.0.ipynb

Lines changed: 883 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/ja/_toc.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ parts:
3636
title: 典型問題集
3737
- caption: Release Notes
3838
chapters:
39+
- file: releases/jijmodeling-2.4.0
3940
- file: releases/jijmodeling-2.3.2
4041
- file: releases/jijmodeling-2.3.1
4142
- file: releases/jijmodeling-2.2.0

docs/ja/basics/expressions.ipynb

Lines changed: 366 additions & 157 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/ja/releases/jijmodeling-2.4.0.ipynb

Lines changed: 862 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

markdowns/en/basics/expressions.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,45 @@ except Exception as e:
257257
print(e)
258258
```
259259

260-
:::{admonition} Division by decision variables
260+
### Alternative syntax: constructing arrays with `genarray`
261+
262+
In the example above, operations involving nontrivial broadcasting, such as `y + z`, result in an (intentional) error.
263+
In such cases, you can use the {py:func}`~jijmodeling.genarray` function to construct the resulting array explicitly by specifying the shape and the expression for each entry:
264+
265+
```{code-cell} ipython3
266+
A = jm.genarray(lambda i, j, k: y[i, j] + z[i, j, k], (N, M, N))
267+
display(A)
268+
problem.infer(A)
269+
```
270+
271+
When using the Decorator API, you can also use a comprehension with `jm.genarray` as follows:
272+
273+
```{code-cell} ipython3
274+
@problem.update
275+
def _(problem: jm.DecoratedProblem):
276+
A = jm.genarray(y[i, j] + z[i, j, k] for i, j, k in (N, M, N))
277+
display(A)
278+
display(problem.infer(A))
279+
```
280+
281+
Only one `for .. in ...` clause is allowed in a `genarray` comprehension.
282+
Be careful, because using multiple `for` clauses as shown below raises an error:
283+
284+
```{code-cell} ipython3
285+
try:
286+
287+
@jm.Problem.define("genarray example")
288+
def _(problem):
289+
N = problem.Natural()
290+
M = problem.Natural()
291+
a = problem.Float(shape=(N, M))
292+
x = problem.BinaryVar(shape=N)
293+
Sums = problem.NamedExpr(jm.genarray(a[i, j] * x[i] for i in N for j in M))
294+
except SyntaxError as e:
295+
print(str(e))
296+
```
297+
298+
::{admonition} Division by decision variables
261299
:class: caution
262300

263301
At the modeling stage, decision variables can appear on either side of arithmetic operators.
@@ -516,6 +554,23 @@ def double_sum_example_alt(problem: jm.DecoratedProblem):
516554
problem += jm.sum(Q[i, j] for (i, j) in jm.product(N, M))
517555
518556
557+
double_sum_example_alt
558+
```
559+
560+
Also, in places such as the right-hand side of `in` in Decorator API comprehensions and the `domain=` keyword argument of `Constraint`, you can omit `jm.product` and represent the Cartesian product with a tuple as follows:
561+
562+
```{code-cell} ipython3
563+
@jm.Problem.define("Double Sum Example (Alt)")
564+
def double_sum_example_alt(problem: jm.DecoratedProblem):
565+
N = problem.Length()
566+
M = problem.Length()
567+
Q = problem.Float(shape=(N, M))
568+
x = problem.BinaryVar(shape=(N, M))
569+
570+
# Note: the Cartesian product is represented by a tuple, not product
571+
problem += jm.sum(Q[i, j] for (i, j) in (N, M))
572+
573+
519574
double_sum_example_alt
520575
```
521576

Lines changed: 287 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,287 @@
1+
---
2+
jupytext:
3+
text_representation:
4+
extension: .md
5+
format_name: myst
6+
format_version: 0.13
7+
jupytext_version: 1.19.1
8+
kernelspec:
9+
display_name: .venv
10+
language: python
11+
name: python3
12+
---
13+
14+
# JijModeling 2.4.0 Release Notes
15+
16+
+++
17+
18+
## Performance Improvements
19+
20+
### Significant performance improvements for dictionaries
21+
22+
We improved the internal processing of dictionaries, achieving a significant performance improvement of about 30x compared with the previous implementation.
23+
If you have been avoiding dictionaries because of performance concerns, this is a good opportunity to try using them.
24+
25+
+++
26+
27+
## Breaking Changes
28+
29+
### Protobuf schema changes
30+
31+
JijModeling 2.4.0 brings the breaking changes to the Protobuf schema for {py:class}`~jijmodeling.Problem`.
32+
As a result, Problems serialized to Protobuf with version 2.4.0 or later can no longer be loaded by JijModeling versions 2.3.x or earlier.
33+
On the other hand, Problems serialized with versions 2.3.x or earlier can be loaded by JijModeling versions 2.4.0 or later.
34+
This may affect data storage and exchange through MINTO, but in that case, updating the dependent JijModeling version to 2.4.0 or later will allow both existing and new data to be loaded without issue.
35+
Also, this only affects direct use of JijModeling's Protobuf schema; there is no particular impact on the OMMX format.
36+
37+
+++
38+
39+
## Feature Enhancements
40+
41+
+++
42+
43+
### Generating arrays with a shape and generator function
44+
45+
Starting with this version, the {py:func}`~jijmodeling.genarray` function can be used to generate arrays by specifying a shape and a generator function.
46+
This is similar to {py:func}`~numpy.fromfunction` in NumPy.
47+
48+
```{code-cell} ipython3
49+
import jijmodeling as jm
50+
51+
52+
problem = jm.Problem("genarray example")
53+
N = problem.Natural("N")
54+
M = problem.Natural("M")
55+
a = problem.Float("a", shape=(N, M))
56+
x = problem.BinaryVar("x", shape=N)
57+
Sums = problem.NamedExpr("Sums", jm.genarray(lambda i, j: a[i, j] * x[i], (N, M)))
58+
59+
60+
problem
61+
```
62+
63+
When using the Decorator API, you can also use a comprehension syntax with `jm.genarray` as follows:
64+
65+
```{code-cell} ipython3
66+
@jm.Problem.define("genarray example")
67+
def problem(problem):
68+
N = problem.Natural()
69+
M = problem.Natural()
70+
a = problem.Float(shape=(N, M))
71+
x = problem.BinaryVar(shape=N)
72+
Sums = problem.NamedExpr(jm.genarray(a[i, j] * x[i] for i, j in (N, M)))
73+
74+
75+
problem
76+
```
77+
78+
Only one `for .. in ...` clause is allowed in a `genarray` comprehension.
79+
The following is an example that raises an error because it uses multiple `for` clauses:
80+
81+
```{code-cell} ipython3
82+
try:
83+
84+
@jm.Problem.define("genarray example")
85+
def problem(problem):
86+
N = problem.Natural()
87+
M = problem.Natural()
88+
a = problem.Float(shape=(N, M))
89+
x = problem.BinaryVar(shape=N)
90+
Sums = problem.NamedExpr(jm.genarray(a[i, j] * x[i] for i in N for j in M))
91+
except SyntaxError as e:
92+
print(str(e))
93+
```
94+
95+
### Support for `min` / `max` along axes
96+
97+
Previously, {py:func}`jm.sum <jijmodeling.sum>` and {py:meth}`Expression.sum <jijmodeling.Expression.sum>` supported taking sums along a specific axis of a multidimensional array via the `axis` keyword argument.
98+
Starting with this version, the same functionality has been added to {py:func}`jm.min <jijmodeling.min>` and {py:func}`jm.max <jijmodeling.max>` as well as their corresponding `Expression` methods.
99+
100+
```{code-cell} ipython3
101+
import jijmodeling as jm
102+
103+
104+
@jm.Problem.define("min/max along axes example")
105+
def problem(problem):
106+
N = problem.Natural()
107+
M = problem.Natural()
108+
a = problem.Float(shape=(N, M))
109+
a_min_0 = problem.NamedExpr(a.min(axis=0), save_in_ommx=True)
110+
a_max_1 = problem.NamedExpr(jm.max(a, axis=1), save_in_ommx=True)
111+
a_min_both = problem.NamedExpr(jm.min(a, axis=[1, 0]), save_in_ommx=True)
112+
113+
114+
problem
115+
```
116+
117+
Now let's create an instance and inspect the included Named Functions together with the value of `a`.
118+
119+
```{code-cell} ipython3
120+
import numpy as np
121+
122+
a_data = np.array([[1, 5, 3], [4, 2, 6]])
123+
compiler = jm.Compiler.from_problem(problem, {"N": 2, "M": 3, "a": a_data})
124+
instance = compiler.eval_problem(problem)
125+
126+
display(instance.named_functions_df)
127+
print(f"a == {a_data}")
128+
```
129+
130+
Since the Named Functions in the OMMX Instance are split apart by index, the table above may be a bit hard to read.
131+
So let's regroup them by variable using `compiler`, build arrays from them, and compare the results.
132+
133+
First, consider `a_min_0 = a.min(axis=0)`, which takes the minimum along axis 0 (columns).
134+
This leaves axis 1 (rows), producing a vector whose entries are the minima of each column.
135+
136+
```{code-cell} ipython3
137+
a_min_0_ids = compiler.get_named_function_id_by_name("a_min_0")
138+
a_min_0_values = [
139+
instance.get_named_function_by_id(a_min_0_ids[(i,)]).function.constant_term
140+
for i in range(3)
141+
]
142+
assert np.all(a_min_0_values == np.min(a_data, axis=0)) # Matches NumPy's behavior!
143+
print(f"a.min(axis=0) == {a_min_0_values}")
144+
```
145+
146+
In contrast, `a_max_1 = a.max(axis=1)` takes the maximum along axis 1 (rows),
147+
producing a vector whose entries are the maxima of each row.
148+
149+
```{code-cell} ipython3
150+
a_max_1_ids = compiler.get_named_function_id_by_name("a_max_1")
151+
a_max_1_values = [
152+
instance.get_named_function_by_id(a_max_1_ids[(i,)]).function.constant_term
153+
for i in range(2)
154+
]
155+
assert np.all(a_max_1_values == np.max(a_data, axis=1)) # Matches NumPy's behavior!
156+
print(f"a.max(axis=1) == {a_max_1_values}")
157+
```
158+
159+
For `a_min_both = a.min(axis=[1, 0])`, the minimum is taken along multiple axes.
160+
Since the input here is two-dimensional, this simply becomes the overall minimum.
161+
162+
```{code-cell} ipython3
163+
a_min_both_ids = compiler.get_named_function_id_by_name("a_min_both")
164+
a_min_both_value = instance.get_named_function_by_id(
165+
a_min_both_ids[()]
166+
).function.constant_term
167+
assert a_min_both_value == np.min(a_data) # Matches NumPy's behavior!
168+
print(f"a.min(axis=[1, 0]) == {a_min_both_value}")
169+
```
170+
171+
## Bugfixes
172+
173+
+++
174+
175+
### Bugfixes in random instance data generation
176+
177+
We fixed the following two bugs in random instance data generation:
178+
179+
#### Placeholders that depend on `NamedExpr` were not handled correctly
180+
181+
We fixed a bug where placeholders whose shape (length) or key set depends on `NamedExpr` were not handled correctly.
182+
For example, consider the following problem:
183+
184+
```{code-cell} ipython3
185+
import jijmodeling as jm
186+
187+
188+
@jm.Problem.define("My Problem")
189+
def problem(problem: jm.DecoratedProblem):
190+
a = problem.Float(ndim=1)
191+
N = problem.NamedExpr(a.len_at(0))
192+
b = problem.Natural(shape=(N, None))
193+
M = problem.NamedExpr(b.len_at(1))
194+
problem += jm.sum(a[i] * b[i, j] for i in N for j in M)
195+
196+
197+
problem
198+
```
199+
200+
In previous versions, calling `generate_random_dataset()` on this `problem` raised an exception. Starting with this release, the data is generated correctly.
201+
202+
```{code-cell} ipython3
203+
problem.generate_random_dataset(seed=17)
204+
```
205+
206+
#### Fixed a bug where generation failed when unused placeholders were present
207+
208+
Data generation failed when there were unused placeholders not included in `used_placeholder()`.
209+
For example, in the following code, `N` is defined but never used, and previous versions raised a runtime exception.
210+
211+
```{code-cell} ipython3
212+
import jijmodeling as jm
213+
214+
problem = jm.Problem("My Problem")
215+
N = problem.Natural("N")
216+
217+
problem.generate_random_dataset(seed=17)
218+
```
219+
220+
Starting with this release, data is generated successfully in cases like the example above.
221+
222+
### Fixed a bug where `latex` specifications were ignored in LaTeX output for decision variable bounds
223+
224+
We fixed a bug where the values of the `latex=` keyword argument for other variables were ignored when outputting decision variable bounds in $\LaTeX$.
225+
226+
```{code-cell} ipython3
227+
import jijmodeling as jm
228+
229+
problem = jm.Problem("LaTeX bugfix example")
230+
L = problem.Float("L", latex=r"\ell")
231+
U = problem.Float("U", latex=r"\mathcal{U}")
232+
x = problem.ContinuousVar("x", lower_bound=L, upper_bound=U)
233+
problem += x
234+
235+
problem
236+
```
237+
238+
In previous releases, the `latex` specifications were ignored in the code above, and the bounds were displayed as $L \leq x \leq U$.
239+
Starting with this release, the settings are preserved as shown above, and the bounds are displayed as $\ell \leq x \leq \mathcal{U}$.
240+
241+
### Fixed a bug where problem evaluation with constraint detection crashed when decision variables were subscripted by tuples
242+
243+
We fixed a bug where `eval_problem` crashed when decision variables were subscripted with tuples and constraint detection was enabled (this is the case by default, or when the `constraint_detection` keyword argument was set to something other than `False`). For example, the following code used to crash in previous versions:
244+
245+
```{code-cell} ipython3
246+
import jijmodeling as jm
247+
248+
249+
@jm.Problem.define("dict-keyed binary var with tuple subscripts")
250+
def problem(problem: jm.DecoratedProblem):
251+
N = problem.Natural()
252+
K = problem.Placeholder(ndim=1, dtype=(jm.DataType.NATURAL, jm.DataType.NATURAL))
253+
x = problem.BinaryVar(dict_keys=K)
254+
255+
problem += problem.Constraint(
256+
"sweeps",
257+
(jm.sum(x[k] for k in K if k[0] == i) <= 1 for i in jm.range(N)),
258+
)
259+
260+
261+
instance_data = {
262+
"N": 3,
263+
"K": [(0, 0), (0, 1), (0, 2), (1, 0), (1, 1)],
264+
}
265+
266+
compiler = jm.Compiler.from_problem(problem, instance_data)
267+
instance = compiler.eval_problem(problem, constraint_detection=True)
268+
```
269+
270+
### Fixed a bug where the sum of binary `{0, 1}` expressions had type Binary instead of Natural
271+
272+
We fixed a bug where an expression that `sum`s another expression of binary type (`{0, 1}`) was typed as `Binary` instead of `Natural`. For example, the sum $\sum_i x_i$ of binary variables $x_0, x_1, \ldots$ can take values of $2$ or more, so the result type had to be `Natural` instead of `Binary`.
273+
274+
```{code-cell} ipython3
275+
import jijmodeling as jm
276+
277+
problem = jm.Problem("Sum of binary example")
278+
N = problem.Natural("N")
279+
x = problem.BinaryVar("x", shape=N)
280+
problem.infer(x.sum())
281+
```
282+
283+
## Other Changes
284+
285+
- Relaxed version bounds to allow installation on any Python 3 version from Python 3.11 onwards.
286+
- Error messages for invalid comprehensions used with the Decorator API in `sum` and similar constructs now report the specific location in the source code.
287+
- {py:meth}`Problem.used_placeholders <jijmodeling.Problem.used_placeholders>` has been deprecated because its purpose is unclear, and {py:class}`~jijmodeling.Compiler` also requires values for all placeholders. Use {py:meth}`Problem.placeholders <jijmodeling.Problem.placeholders>` instead.

0 commit comments

Comments
 (0)