Tips#
Adding Terms to the Objective Function#
The objective function of Problem
is designed to be overwritten. Therefore, be careful that adding a new objective function will overwrite the existing one.
import jijmodeling as jm
c = jm.Placeholder("c", ndim=1)
N = c.len_at(0)
x = jm.BinaryVar("x", shape=(N,))
i = jm.Element("i", (0, N))
problem = jm.Problem("my problem")
problem += jm.sum(i, c[i] * x[i]) # Set the objective function
# If you want to add another variable or expression later,
# you need to start by creating a new objective function
d = jm.Placeholder("d", ndim=1)
M = d.len_at(0)
y = jm.BinaryVar("y", shape=(M,))
j = jm.Element("j", (0, M))
problem += jm.sum(i, c[i] * x[i]) + jm.sum(j, d[j] * y[j])
If you want to construct a more complex objective function, it is recommended to assemble simple terms or expressions before writing out the final expression. For example:
sum_of_xs = jm.sum(i, c[i] * x[i])
sum_of_ys = jm.sum(j, d[j] * y[j])
problem += sum_of_xs + sum_of_ys
Caution
Currently, jijmodeling
does not support mathematical models with multiple objective functions.
Defining Element
Along a Set#
Some users may want to create an index \(i \in \{0, 1, 2\}\) using Python’s set Set
as follows:
import jijmodeling as jm
i = jm.Element("i", {0,1,2}) # This code will cause an error
However, the above notation is not supported in jijmodeling
. In this example, you need to express it as an interval. (Since the interval of Element
is a half-open interval, you need to write (0, 3)
to represent \(\{0, 1, 2\}\))
i = jm.Element("i", (0, 3))
For more complex use cases (such as when the index is not just a continuous integer), consider whether it can be expressed in one of the following two ways:
The first option is to create a one-dimensional Placeholder
representing the set. This option is useful when expressing a set containing discontinuous integers like \(E = \{2, 4, 10, 35, 36\}\). This allows Element
to function as if it were using a set. Using this method, an Element
representing \(e \in E\) can be defined as follows:
E = jm.Placeholder("E", ndim=1)
e = jm.Element("e", E)
However, note that the specific values within the set do not appear in the mathematical model. You need to specify the actual values of E
when converting the mathematical model to an instance.
Another option is to use conditions that restrict valid values within the range. This option is useful when using a set of integers that follow a rule. For how to apply conditions, refer to here.
Handling Conditional Element
#
Often, the index of a sum in a mathematical model is conditional. For example, conditions like \(i \neq j\). These conditions are specified when defining the sum or constraint, not when creating the index. In jijmodeling
, you can define a conditional index for sum
as a tuple (<element>, <condition>)
in the first argument. In this case, <element>
is used as the index, and <condition>
is set as the condition, and only those that satisfy <condition>
are summed.
For example, consider the sum of \(x_i\) for even \(i\). In jijmodeling
, it can be defined as follows:
import jijmodeling as jm
i = jm.Element("i", (0, 100))
x = jm.BinaryVar("x", shape=(100,))
sum_over_even_is = jm.sum((i, i % 2 == 0), x[i])
It is also possible to compare with other Element
used in the same sum. Additionally, the first argument of sum
can be input in the form of [[index 1, condition of index 1), (index 2, condition of index 2), ...]]
. Note that condition of index 1
cannot use index 2
, so be careful about the order of indices and conditions. As a specific example, the following shows a sum for two indices \(i, j\) where \(i \neq j\) in jijmodeling
.
import jijmodeling as jm
i = jm.Element("i", (0, 100))
j = jm.Element("j", (0, 100))
x = jm.BinaryVar("x", shape=(100, 100))
jm.sum([i, (j, j!= i)], x[i, j])
Similarly, the tuple notation can also be used in the forall
argument of Constraint
.
jm.Constraint("c1", x[i, j] - x[j, i] >= 0, forall=[i, (j, j != i)])
Additionally, logical operations can be applied to indices, not just comparison operations. Here, logical operations refer to logical AND &
, logical OR |
, and exclusive OR ^
.
jm.sum((i, (i % 2 == 0) | (i % 5 == 0)), x[i, 0])
Handling Constraints Bounded by Inequalities on Both Sides#
In mathematical optimization, there are cases where you want to handle constraints bounded by inequalities on both sides, such as \(l \leq x + y \leq u\). These constraints are not directly supported in jijmodeling
, and attempting to implement them as is will result in an exception Converting <class> to boolean is unsupported
.
Therefore, in jijmodeling
, you need to split such constraints into two and describe each as a single inequality.
import jijmodeling as jm
l, u = jm.Placeholder("l"), jm.Placeholder("u")
x = jm.IntegerVar("x", lower_bound=0, upper_bound=10)
y = jm.IntegerVar("y", lower_bound=5, upper_bound=20)
problem = jm.Problem("problem")
problem += jm.Constraint("greater than l", l <= x + y)
problem += jm.Constraint("less than u", x + y <= u)
problem
Using Dependent Variables#
Suppose you want to write a constraint like the following in jijmodeling
:
This constraint has \(y_i\) as a dependent variable. In jijmodeling
, it is not difficult to write such dependent variables. Additionally, if necessary, you can display it as \(y_i\) in LaTeX using set_latex
.
import jijmodeling as jm
a = jm.Placeholder("a", ndim=1)
b = jm.Placeholder("b")
N = a.len_at(0, latex="N")
x = jm.BinaryVar("x", shape=(N,))
i = jm.Element("i", belong_to=N)
c = jm.Placeholder("c")
y = a[i] * x[i] + b
y.set_latex("y_i")
jm.Constraint("constraint", y <= c, forall=i)
However, the above code lacks flexibility. This is because \(y\) is explicitly defined using the index \(i\), and it cannot be reused if a similar expression is used in other parts of the mathematical model. To ensure this flexibility, it is better to use Python functions or lambda
expressions.
y = lambda e: a[e] * x[e] + b
jm.Constraint("constraint", y(i) <= c, forall=i)
def y(e: jm.Element):
y = a[e] * x[e] + b
y.set_latex("y_{e.name}")
return y
jm.Constraint("constraint", y(i) <= c, forall=i)
Handling Multi-dimensional Variables with Different Lower and Upper Bounds#
When defining multi-dimensional decision variables, you usually specify lower_bound
and upper_bound
as scalar values. In this case, all decision variables are set with the same lower and upper bounds.
N = jm.Placeholder("N")
M = jm.Placeholder("M")
x = jm.IntegerVar("x", shape=(N, M), lower_bound=0, upper_bound=5)
In the above code, N * M
integer variables x
are defined with a lower bound of 0 and an upper bound of 5. So, what if you want to define them with different lower and upper bounds?
One way to solve this problem is to specify lower_bound
and upper_bound
with Placeholder
. For a 0-dimensional Placeholder
, this works the same as when you provide a normal numeric literal. On the other hand, you can also use a Placeholder
with the same dimensions as the decision variables, and specify the lower and upper bounds for each variable with that Placeholder
.
Below is an example of setting the upper bound of the variable \(x_{i,j}\) individually to \(ub_{i,j}\).
import jijmodeling as jm
ub = jm.Placeholder("ub", ndim=2)
N = ub.len_at(0, latex="N")
M = ub.len_at(1, latex="M")
# Define integer variable x with lower bound 0 and upper bound ub
x = jm.IntegerVar("x", shape=(N,M), lower_bound=0, upper_bound=ub)
Note that the dimensions and shapes of the decision variables and Placeholder
must match. Also, make sure that all decision variables have valid lower and upper bounds. In the above code, N
and M
are defined based on ub
, so it is guaranteed that their shapes match.
Additionally, if you want to use the transpose of Placeholder
or specify other axes as the lower and upper bounds for some reason, you can use a special syntax with Element
. For example, if you want to set the upper bound of the variable \(x_{i,j}\) to the value of \(ub_{j,i}\) by reversing the indices, you can do this by defining an Element
that matches the axes and specifying the upper bound as ub[j, i]
as follows:
ub = jm.Placeholder("ub", ndim=2)
N = ub.len_at(0, latex="N")
M = ub.len_at(1, latex="M")
i = jm.Element("i", N)
j = jm.Element("j", M)
x = jm.IntegerVar("x", shape=(N, M), lower_bound=0, upper_bound=ub[j, i])
As mentioned earlier, make sure that all variables have valid lower and upper bounds. Additionally, note that the above code only makes sense when N
and M
are equal. Also, note that this syntax cannot be used in sums and constraints.
Handling Sums for Indices of Different Sizes#
In this tip, we will explain methods related to multi-dimensional Element
and indexing, which may seem a bit niche.
Suppose you have a 2-dimensional decision variable \(x\) and want to write a constraint like the following:
Here, \(A\) is a 2-dimensional “irregular array” with \(N\) rows. Therefore, you can interpret \(A\) as a “list of lists” and use each list as an index.
To clarify what this constraint means, let’s consider an example with \(A = [[1, 2, 3], [0, 1, 4, 5], [2, 3, 5]]\). Specifically, the constraint can be written as follows:
In jijmodeling
, you can write this constraint as follows:
import jijmodeling as jm
A = jm.Placeholder("A", ndim=2)
N = A.len_at(0, latex="N")
n = jm.Element("n", N) # Number of rows in the "irregular" array
a = jm.Element("a", A[n]) # Index for each row
x = jm.BinaryVar("x", shape=(3,6))
jm.Constraint("constraint", jm.sum(a, x[n, a]) == 0, forall=n)
In the above example, we defined the shape of x
with specific numbers. In an actual mathematical model, it is recommended to define the shape as a Placeholder
or related to other parameters to avoid index errors. To correctly define such constraints, it is better to define enough decision variables x
. Specifically, you can replace (3, 6)
in the above code with (number of rows in A, maximum number of columns in A)
. However, there is a problem that the maximum number of columns in A
is unknown at the stage of constructing the mathematical model. Therefore, it is better to define an additional Placeholder
and make it possible to specify it as instance data.
# Define a Placeholder that means `maximum number of columns in A`
max_A = jm.Placeholder("max_A")
x = jm.BinaryVar("x", shape=(N, max_A + 1))
problem = jm.Problem("problem")
problem += jm.Constraint("constraint", jm.sum(a, x[n, a]) == 0, forall=n)
It is recommended to construct instance data for use with jijmodeling_transpiler
or JijZept as follows.
# Create an "irregular" array
data_A = [
[1, 2, 3],
[0, 1, 4, 5],
[2, 3, 5],
# ...
]
# Get the maximum number of columns in the "irregular" array
data_max_A = max(max(An) for An in data_A)
instance_data = {
"A": data_A,
"max_An": data_max_A,
}