Arithmetic and Conditional Expressions#

In the preceding chapters, we learned about expressions and types in JijModeling and how to declare variables. We will now look at how to construct more complex expressions, including arithmetic expressions for addition, subtraction, multiplication, and division, as well as conditional expressions involving comparisons.

import jijmodeling as jm

Arithmetic Operations#

Python’s built-in arithmetic operations (addition, subtraction, multiplication, division, and so on, including +, -, *, /, and %) can also be used with JijModeling expressions. Operations between numeric expressions work as expected. Under certain conditions, operations can also be performed between multidimensional arrays and between TotalDict objects with identical key sets. Specifically, arithmetic operations are supported for the following combinations, regardless of operand order:

  1. Two scalars

  2. A scalar and a multidimensional array

  3. A scalar and a dictionary

  4. Two multidimensional arrays with the same shape

  5. Two total dictionaries (TotalDict) with the same key set

Broadcasting in JijModeling

Cases (2) and (3) correspond to broadcasting, as seen in NumPy, where a scalar is applied to every element of a collection. Cases (4) and (5), on the other hand, perform operations between corresponding elements. NumPy also supports operations between more general combinations of shapes, such as \((N, M, L)\) and \((M, L)\). Although such generalized NumPy broadcasting provides concise notation, its intent can often be unclear when the code is revisited later. For this reason, JijModeling intentionally limits broadcasting and supports it only when the operation is considered unambiguous to everyone.

This is easier to understand through examples.

problem = jm.Problem("Arithmetic Operations")
x = problem.BinaryVar("x", description="Scalar decision variable")
N = problem.Length("N")
M = problem.Length("M")
y = problem.IntegerVar(
    "y", lower_bound=0, upper_bound=10, shape=(N, M), description="2D array decision variable"
)
z = problem.ContinuousVar(
    "z",
    lower_bound=-1,
    upper_bound=42,
    shape=(N, M, N),
    description="3D array decision variable",
)
S = problem.TotalDict("S", dtype=float, dict_keys=N, description="Scalar total dictionary")
s = problem.ContinuousVar("s", lower_bound=0, upper_bound=10, dict_keys=N)
W = problem.Float("w", shape=(N, M))

problem
\[\begin{array}{rl} \text{Problem}\colon &\text{Arithmetic Operations}\\\displaystyle \min &\displaystyle 0\\&\\\text{where}&\\&\text{Decision Variables:}\\&\qquad \begin{alignedat}{2}{s}_{i}&\in \mathbb{R}\;\left(0\leq {s}_{i}\leq 10\right)&\qquad &\text{a dictionary of }\text{continuous}\text{ decision variables}\\&\forall i\in \left\{0,\ldots ,N-1\right\}&&\\&&&\\x&\in \left\{0,1\right\}&\qquad &\text{a binary decision variable}\\&&&\text{Scalar decision variable}\\&&&\\{y}_{i,j}&\in \mathbb{Z}\;\left(0\leq {y}_{i,j}\leq 10\right)&\qquad &\text{a }2\text{-dim array of }\text{integer}\text{ decision variables}\\&\forall i\in \left\{0,\ldots ,N-1\right\},\;\forall j\in \left\{0,\ldots ,M-1\right\}&&\text{2D array decision variable}\\&&&\\{z}_{i,j,k}&\in \mathbb{R}\;\left(-1\leq {z}_{i,j,k}\leq 42\right)&\qquad &\text{a }3\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\},\;\forall k\in \left\{0,\ldots ,N-1\right\}&&\text{3D array decision variable}\\\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}\\&&&\\{S}_{i}&\in \mathbb{R}&\qquad &\text{a dictionary of placeholders in }\mathbb{R}\\&\forall i\in \left\{0,\ldots ,N-1\right\}&&\text{Scalar total dictionary}\\&&&\\{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}\end{array} \]

Allowed Examples#

problem.infer(x + 1)  # OK: addition of two scalars
\[\mathbb{N}\]
problem.infer(y - x)  # OK: subtraction of a scalar from a multidimensional array
\[\mathop{\mathrm{Array}}\left[N\times M;\mathbb{Z}\right]\]
problem.infer(S * x)  # OK: multiplication of a dictionary by a scalar
\[\mathop{\mathrm{TotalDict}}\left[N;\mathbb{R}\right]\]
problem.infer(y / W)  # OK: division of two arrays with the same shape (N, M)
\[\mathop{\mathrm{Array}}\left[N\times M;\mathbb{R}\right]\]
problem.infer(S + s)  # OK: addition of total dictionaries with the same key set
\[\mathop{\mathrm{TotalDict}}\left[N;\mathbb{R}\right]\]

Disallowed Examples#

try:
    # ERROR: multiplication of a dictionary and an array
    problem.infer(S * y)
except Exception as e:
    print(e)
Traceback (most recent last):
    while inferring the type of expression `S * y`,
        defined at File "/tmp/ipykernel_748/1638303748.py", line 3, col 19-24
    while inferring the type of expression `S * y`,
        defined at File "/tmp/ipykernel_748/1638303748.py", line 3, col 19-24
    while checking if types `TotalDict[N; float]` and `Array[N, M; int!]` can be combined with numeric operator `*`,
        defined at File "/tmp/ipykernel_748/1638303748.py", line 3, col 19-24

File "/tmp/ipykernel_748/1638303748.py", line 3, col 19-24:

    3  |      problem.infer(S * y)
                            ^^^^^

error[E-TE0015] `numeric operator *` is not supported between types `TotalDict[N; float]` and `Array[N, M; int!]`

Hint: You can read the description and possible fix at https://jij-inc-jijmodeling.readthedocs-hosted.com/en/stable/error_codes/error/E-TE0015.html
try:
    # ERROR: operation between arrays with different shapes
    problem.infer(y + z)
except Exception as e:
    print(e)
Traceback (most recent last):
    while inferring the type of expression `y + z`,
        defined at File "/tmp/ipykernel_748/426143848.py", line 3, col 19-24
    while inferring the type of expression `y + z`,
        defined at File "/tmp/ipykernel_748/426143848.py", line 3, col 19-24
    while checking if types `Array[N, M; int!]` and `Array[N, M, N; float!]` can be combined with numeric operator `+`,
        defined at File "/tmp/ipykernel_748/426143848.py", line 3, col 19-24

File "/tmp/ipykernel_748/426143848.py", line 3, col 19-24:

    3  |      problem.infer(y + z)
                            ^^^^^

error[E-TE0015] `numeric operator +` is not supported between types `Array[N, M; int!]` and `Array[N, M, N; float!]`

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

Alternative Syntax: Constructing Arrays with genarray or gendict#

In the example above, an operation involving nontrivial broadcasting, such as y + z, intentionally results in an error. In such cases, you can construct the desired array or dictionary by using genarray() or gendict() and explicitly specifying its shape or key set and the expression for each element:

A = jm.genarray(lambda i, j, k: y[i, j] + z[i, j, k], (N, M, N))
display(A)
problem.infer(A)
\[{\left( {y}_{i,j}+{z}_{i,j,k}\right) }_{\begin{subarray}{l} i\in \left\{0,\ldots ,N-1\right\}\\j\in \left\{0,\ldots ,M-1\right\}\\k\in \left\{0,\ldots ,N-1\right\}\end{subarray} }\]
\[\mathop{\mathrm{Array}}\left[N\times M\times N;\mathbb{R}\right]\]

When using the Decorator API, you can also use a comprehension as follows:

@problem.update
def _(problem: jm.DecoratedProblem):
    A = jm.genarray(y[i, j] + z[i, j, k] for i, j, k in (N, M, N))
    display(A)
    display(problem.infer(A))
\[{\left( {y}_{i,j}+{z}_{i,j,k}\right) }_{\begin{subarray}{l} i\in \left\{0,\ldots ,N-1\right\}\\j\in \left\{0,\ldots ,M-1\right\}\\k\in \left\{0,\ldots ,N-1\right\}\end{subarray} }\]
\[\mathop{\mathrm{Array}}\left[N\times M\times N;\mathbb{R}\right]\]

For details, see the relevant section of Operations on Arrays and Dictionaries.

Division by Decision Variables

When constructing a model, expressions that may contain decision variables can appear on either side of addition, subtraction, multiplication, and division. However, compiling an expression in which a decision variable appears on the right-hand side of division—such as N / x in the example above—currently results in an error. The notation itself is allowed because some solvers support division by decision variables through specific encodings, but JijModeling and OMMX do not currently support those encodings. In the future, JijModeling and OMMX are expected to support specifying such encodings, allowing some of these cases to be compiled into instances.

Elementary Transcendental Functions

In addition to arithmetic operations, JijModeling expressions support elementary transcendental functions, including trigonometric functions (sin(), cos(), and tan()) and logarithmic functions (log2(), log10(), and ln()). These functions can be applied to expressions regardless of whether they contain decision variables. Currently, however, compiling an instance results in an error if one of these functions is applied to an expression containing a decision variable.

Comparison Operations#

Equality operators (== and !=) and ordering operators (<, <=, >, and >=) can also be used with JijModeling expressions.

If neither side of a comparison operator contains a decision variable, the result is inferred as an expression of the Boolean type Bool. If at least one side may contain a decision variable, the result is treated as a special comparison type. This distinction is necessary because constraint definitions must be able to compare expressions containing decision variables, while comparisons used in comprehensions and similar constructs must evaluate to a definite Boolean value.

Numeric scalars support both equality and ordering comparisons, while category-label values support only == and !=. When a comparison operator is applied to arrays or dictionaries, their element types must support the comparison and the same shape or key-set conditions as arithmetic operations must be satisfied.

problem.infer(x == y)  # OK: equality comparison between a scalar and an array
\[\mathop{\mathrm{Comparison}}\left[\left\{0, 1\right\},\mathop{\mathrm{Array}}\left[N\times M;\mathbb{Z}\right]\right]\]
problem.infer(N <= N)  # OK: ordering comparison between two scalars
\[\mathrm{Bool}\]
problem.infer(y > W)  # OK: comparison between arrays with the same shape
\[\mathop{\mathrm{Comparison}}\left[\mathop{\mathrm{Array}}\left[N\times M;\mathbb{R}\right],\mathop{\mathrm{Array}}\left[N\times M;\mathbb{R}\right]\right]\]

Logical Operations#

JijModeling supports logical operations such as conjunction (“and”), disjunction (“or”), and negation (“not”). Because Python’s logical operators and, or, and not cannot be overloaded, use the bitwise operators & (and), | (or), and ~ (not), or the functions jijmodeling.band() (and), jijmodeling.bor() (or), and jijmodeling.bnot() (not).

Beware of Bitwise Operator Precedence

Unlike and and or, & and | have higher precedence than == and !=. For example, a == b & c == d is interpreted as a == (b & c) == d. When using & or |, always enclose each comparison in parentheses, as in (a >= b) & (c == d).

The following example takes the sum only when i is even or j is odd:

@jm.Problem.define("Sum Example")
def problem(problem: jm.DecoratedProblem):
    N = problem.Length()
    M = problem.Length()
    a = problem.Float(shape=(N, M))
    x = problem.BinaryVar(shape=(N, M))
    problem += jm.sum(
        a[i, j] * x[i, j] for i in N for j in M if (i % 2 == 0) | (j % 2 == 1)
    )


problem
\[\begin{array}{rl} \text{Problem}\colon &\text{Sum Example}\\\displaystyle \min &\displaystyle \sum _{i=0}^{N-1}{\sum _{\substack{j=0\\i\bmod 2=0\lor j\bmod 2=1}}^{M-1}{{a}_{i,j}\cdot {x}_{i,j}}}\\&\\\text{where}&\\&\text{Decision Variables:}\\&\qquad \begin{alignedat}{2}{x}_{i,j}&\in \left\{0,1\right\}&\qquad &\text{a }2\text{-dim array of }\text{binary}\text{ decision variables}\\&\forall i\in \left\{0,\ldots ,N-1\right\},\;\forall j\in \left\{0,\ldots ,M-1\right\}&&\\\end{alignedat}\\&\\&\text{Placeholders:}\\&\qquad \begin{alignedat}{2}{a}_{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\}&&\\&&&\\M&\in \mathbb{N}&\qquad &\text{a scalar placeholder in }\mathbb{N}\\&&&\\N&\in \mathbb{N}&\qquad &\text{a scalar placeholder in }\mathbb{N}\\\end{alignedat}\end{array} \]

Example of a More Complex Conditional Expression

For a more realistic and complex conditional expression built with logical operations, see “Deep Space Network Scheduling” in the JijZept Typical Problem Collection.