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])))'
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:
Editor assistance and static checking via Python type hints
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 numbers, integers, real-valued scalars, and related numeric types. |
Category label types |
\(L\) |
|
Sets of labels provided later by users. |
Higher-dimensional array types |
\(\mathrm{Array}[N_1, \ldots, N_k; A]\) |
|
Multidimensional arrays of shape \(N_1 \times \cdots \times N_k\) with elements of type |
Dictionary types |
\(\mathrm{TotalDict}[K; V]\) / \(\mathrm{PartialDict}[K; V]\) |
|
Dictionaries with key set \(K\) and value type \(V\). |
Tuple types |
\(T \times U\) |
|
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:
When a term is added to a problem’s objective
When a constraint is declared via
Problem.Constraint()When it appears in
ndim,shape, ordict_keysWhen compiling to an instance via
Problem.eval()orCompilerWhen 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)
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 |
|---|---|
A type that can be converted to |
|
A function that takes one or more |
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.