JijModeling Expressions and Types#

In JijModeling, models are described by combining expressions, and expressions are classified into several kinds, or types. In addition to Python type hints, JijModeling provides its own more detailed type checker, which can detect common modeling mistakes during construction. The following chapters explain how to construct individual expressions in detail. As preparation, this chapter provides a brief overview of expressions and types in JijModeling.

Tip

We focus on basic common patterns here. For a complete list of expressions, see the API reference for the Expression class and top-level functions in the jijmodeling module.

The Cheat Sheet also provides more complex examples, so it is worth checking after reading this chapter.

import jijmodeling as jm

What is an expression?#

JijModeling separates mathematical model definitions from input data to achieve various features and efficiency. As a result, modeling in JijModeling does not embed input data directly into a mathematical model. Instead, you first build a “program that becomes a concrete mathematical model only after input data is given”, then provide input data later and compile it into a specific mathematical model—an instance. JijModeling calls this “program” an expression.

More precisely, JijModeling expressions do not store concrete computation results, but keep an abstract syntax tree (AST) built by connecting decision variables, placeholders, constants, and other elements through operations. Consider the following example:

@jm.Problem.define("Test Problem")
def ast_examples(problem: jm.DecoratedProblem):
    N = problem.Length()
    x = problem.BinaryVar()
    y = problem.IntegerVar(lower_bound=0, upper_bound=42, shape=(N,))

    z = x + y[0]
    w = jm.sum(y[i] for i in N)
    display(repr(z))
    display(repr(w))
'Expression(x + y[0])'
'Expression(sum(stream(N).map(lambda i: y[i])))'
Python variables can bind arbitrary expressions and variables. Expressions are represented as syntax trees with operators as nodes and constants or parameters as leaves.

Fig. 5 Decision variables, placeholders, and syntax trees bound to Python variables#

Figure 5 visualizes the definition of Test Problem. Decision variables and placeholders in the model such as \(x, y, N\) correspond to Python variables x, y, N. This illustrates an ambiguity: when we say “variable”, it can mean either a parameter in the model or a Python variable that temporarily binds it. Expressions like z = x + y[0] and w = jm.sum(y[i] for i in N) are represented as symbolic ASTs that reference these variables. In this way, a JijModeling expression combines individual components such as constants, placeholders, and decision variables through various operations.

Function calls and method calls are equivalent for expressions

For an Expression object A, unary operations can be written as prefix function calls like jm.log(A) or as postfix method calls like A.log(). Both construct exactly the same expression, so use whichever you prefer. The same applies to DecisionVar and Placeholder. However, Python builtin numbers do not support method calls, so for such cases you must use function calls like jm.log(2).

Types of expressions in JijModeling#

In JijModeling, expressions are classified by type and validated as needed. You can use JijModeling without understanding the type system in detail. Still, it is useful to know how the type checks are performed when you formulate models. This chapter gives a brief overview.

JijModeling actually performs type checks in two stages:

  1. Editor assistance and static checking via Python type hints

  2. A built-in type checker in JijModeling during model construction

(1) is bundled as Python code in the library and enables editor completion and static checks with tools like Pyright, ty, and pyrefly. However, Python type hints cannot express all constraints (for example, validating array index sizes). To compensate, JijModeling includes (2), its own more expressive type checker.

The checker in (2) is not invoked directly by users. It is called when you add constraints or objective terms, declare shape for decision variables/placeholders, and so on, and it automatically validates modeling mistakes before any data is provided. Python type hints distinguish API objects such as Expression, Placeholder, and DecisionVar. They cannot, however, fully represent detailed information such as an expression’s shape or dictionary key set, or whether it contains decision variables. JijModeling’s built-in type checker checks this information as well. In other words, editor and Jupyter Notebook checks based on Python type hints are relatively coarse, while finer checks happen during model construction.

There are several expression types in JijModeling; representative ones are listed below:

Kind

Notation (example)

Textual example

Description

Numeric types

\(\mathbb{N}, \mathbb{Z}, \mathbb{R}\)

natural, int, float

Natural numbers, integers, real-valued scalars, and related numeric types.

Category label types

\(L\)

CategoryLabel("L")

Sets of labels provided later by users.

Higher-dimensional array types

\(\mathrm{Array}[N_1, \ldots, N_k; A]\)

Array[N1, ..., Nk; A]

Multidimensional arrays of shape \(N_1 \times \cdots \times N_k\) with elements of type A.

Dictionary types

\(\mathrm{TotalDict}[K; V]\) / \(\mathrm{PartialDict}[K; V]\)

TotalDict[K; V], PartialDict[K; V]

Dictionaries with key set \(K\) and value type \(V\).

Tuple types

\(T \times U\)

Tuple[int, float]

Fixed-length tuples with per-component types.

With these in mind, let’s look at operations that commonly appear in modeling.

When errors are raised

JijModeling’s built-in type checking is performed not right after an expression is created, but at the following times:

  1. When a term is added to a problem’s objective

  2. When a constraint is declared via Problem.Constraint()

  3. When it appears in ndim, shape, or dict_keys

  4. When compiling to an instance via Problem.eval() or Compiler

  5. When type inference is explicitly triggered via Problem.infer()

This is because expression types are determined only when placed in context. So even if an expression is “invalid”, it does not necessarily throw an error at construction time.

Below, we use Problem.infer() to show valid and invalid examples. This method infers the type of a given expression based on the decision variables and placeholders defined in the Problem, and it raises a type error for invalid expressions. Let’s look at an example. Here, we add a binary variable \(x\) and an integer \(N\), so \(x + N\) is inferred as an integer-type expression \(\mathbb{Z}\).

problem = jm.Problem("Type Inference Example")
x = problem.BinaryVar("x", description="Scalar decision variable")
N = problem.Integer("N")

problem.infer(x + N)  # OK! (scalar addition)
\[\mathbb{Z}\]

On the other hand, a scalar value cannot be added to a string, so the following example raises an error.

try:
    # ERROR! (string and scalar cannot be added)
    problem.infer(x + "hoge")
except Exception as e:
    print(e)
Traceback (most recent last):
    while inferring the type of expression `x + "hoge"`,
        defined at File "/tmp/ipykernel_808/594888127.py", line 3, col 19-29
    while inferring the type of expression `x + "hoge"`,
        defined at File "/tmp/ipykernel_808/594888127.py", line 3, col 19-29
    while checking if types `binary!` and `Literal["hoge"]` can be combined with numeric operator `+`,
        defined at File "/tmp/ipykernel_808/594888127.py", line 3, col 19-29

File "/tmp/ipykernel_808/594888127.py", line 3, col 19-29:

    3  |      problem.infer(x + "hoge")
                            ^^^^^^^^^^

error[E-TE0015] `numeric operator +` is not supported between types `binary!` and `Literal["hoge"]`

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

What is the relationship between Expression and ExpressionLike / ExpressionFunction?

In the API reference and editor completions/docs, you may see type names such as ExpressionLike and ExpressionFunction. These are dummy shorthand types that do not exist in the library implementation, and are used to represent types that can be converted to Expression, or functions from Expression to Expression. Specifically, you can think of them as follows:

Type name

Description

ExpressionLike

A type that can be converted to Expression. Depending on the context, this includes Expression itself, Placeholder, DecisionVar, NamedExpr, as well as Python numbers, strings, tuples, lists, dictionaries, NumPy arrays, and so on.

ExpressionFunction

A function that takes one or more Expression objects and returns a Expression. In Python type hints, only up to 5 arguments are enumerated, but in practice there is no limit on the number of arguments.

Placeholders and decision variables as expressions#

As described in Variables in JijModeling, decision variables and placeholders are defined with methods like Problem.BinaryVar and Problem.Placeholder. These methods return DecisionVar and Placeholder objects that hold metadata, but when used in expression building they are automatically converted into Expression objects. In the Test Problem example above, Python variables x and y are DecisionVar objects, but in z = x + y[0], they are converted to expressions that represent a decision variable and an array of decision variables. Constants like 0 are plain Python numbers, but they are also automatically converted when they appear in expressions.

How to construct expressions#

The following chapters explain specific ways to construct expressions.

Arithmetic and Conditional Expressions

Explains how to construct expressions with arithmetic operations such as addition, subtraction, multiplication, and division, and with ordering and equality comparisons.

Operations on Arrays and Dictionaries

Explains how to declare multidimensional arrays and dictionaries and access their elements.

Folding and Streams

Introduces reductions over arrays and dictionaries using streams and the construction of expressions using logical operations.

For concrete examples of these constructs, see Cheat Sheet.