I am trying to build a MIP model in Pyomo and having trouble creating an Or constraint. An OR constraint r = or{x1, ..., xn} states that the binary resultant variable r should be 1 if and only if any of the operand variables x1, ..., xn is equal to 1. I failed no such function that can create OR constraint in Pyomo, so I use my own code
m = ConcreteModel()
m.r = Var(within=Binary)
m.x1 = Var(within=Binary)
m.x2 = Var(within=Binary)
m.or_constraint = Constraint(expr=m.r==min(sum(m.x1, m.x2), 1)
Then ran the code and got the error msg that variables m.x1 and m.x2 should be initialized. I initialized them with 1 and found that m.or_constraint degraded to force m.r equal to 1. In other words, m.or_constraint just used m.x1 and m.x2 initial value to build the constraint and never updated in the process of solving the MIP problem.
I tried different expressions in Pyomo to create this constraint, like call a rule function in the constraint definition. However, every time I got the same result.
Could you direct me to create OR constraint in Pyomo?
The relation
y = x(1) or x(2) or ... or x(n) ( same as y = max{x(i)} )
y, x(i) ∈ {0,1} ( all binary variables )
can be formulated as a set of n+1 linear inequalities
y <= sum(i, x(i))
y >= x(i) for all i
It is also possible to write this as just two constraints:
y <= sum(i,x(i))
y >= sum(i,x(i))/n
The first version is tighter, however. That is the one I typically use.
Note: the first version is so tight that we even can relax y to be continuous between 0 and 1. Whether this is advantageous for the solver, requires a bit of experimentation. For the second version, it is required that y is binary.
Actually, there is another way to create a 'or' statement by using Pyomo with Binary(only for two constraints).
Obj: Min(OF)=f(x)
s.t. x<=A or x<=B
We can add a binary Number(Y) to form this model.M is a number which great enough(100000).
Obj: Min(OF)=f(x,y)
s.t. X<= A+M*y
X<=B+(1-Y)M
Y=0,1
Related
I am working on a package selection problem. I have to put a constraint on the final result, like ‘top3 brands among selected products should account for less than 50%’.
I try to implement that on Pulp. But it seems CBC solver do not support such constraint. Please help me. How can I put such constraint? Or should I switch to another solver?
It depends a bit on what exactly "top 3 brands" is. If you mean the sum of the three largest x[i] should be less than 50% of the total, then this can be linearized as follows:
y1 >= x[i] for all i
y2 >= x[i]-M*delta1[i] for all i
y3 >= x[i]-M*delta2[i] for all i
y1+y2+y3 <= sum(i,x[i])/2
sum(i, delta1[i]) = 1
sum(i, delta2[i]) = 2
delta1[i],delta2[i] ∈ {0,1}
This can be solved with CBC. Here M is a large enough constant (called big-M) and delta1,delta2 are binary variables. Note that y1,y2,y3 are bounds on the largest three values, so interpreting these values may not always be obvious. Basically, the definition of these values is:
y1 : variable at least as large as the largest x[i]
y2 : variable at least as large as the second largest x[i]
y3 : variable at least as large as the third largest x[i]
Other and better formulations exist (but this one is a bit more intuitive [for me that is]).
A simpler approach would be to use sum_largest(x,k) in CVXPY, e.g.
sum_largest(x,3) <= sum(x)/2
I am making an AbstractModel in Pyomo and I want to impose as a constraint the following:
My decision variables are Xij, defined as: model.x = Var(model.I, model.J, domain=NonNegativeIntegers)
The constraint is that Xij must be zero or a multiple of 50.
I am trying to do so by using the remainder but when making the constraint with % or something similar I obtain that % does not support using int.
How would you propose this constraint?
Example:
Example of constraint that I have tried
"Xij must be zero or a multiple of 50"
variables
y(i,j) ∈ {0,1,2,3...}
x(i,j) ≥ 0
constraint
x(i,j) = 50*y(i,j)
I have a system that simplifies to the following: a power generation and storage unit are being used to meet demand. The objective function is the cost to produce the power times the power produced. However, the power produced is stratified into bins of different costs, and the "clearing price" to produce power is the cost at the highest bin produced each hour:
T = np.arange(5, dtype=int)
produce_cap = 90 # MW
store_cap = 100 # MWh
store_init = 0 # MWh
m = pyo.ConcreteModel()
m.T = pyo.Set(initialize=T) # time, hourly
m.produce = pyo.Var(m.T, within=pyo.NonNegativeReals, initialize=0) # generation
m.store = pyo.Var(m.T, within=pyo.Reals, initialize=0) # storage
stack = np.arange(10, 91, 20) # cumulative sum of generation subidivisions
price = np.arange(0.9, 0.01, -0.2) # marginal cost for subdivision of generation
demand = np.asarray([35, 5, 75, 110, 15]) # load to meet
m.produce_cap = pyo.Constraint(m.T, rule=lambda m, t: m.produce[t] <= produce_cap)
m.store_max = pyo.Constraint(m.T, rule=lambda m, t: m.store[t] <= store_cap)
m.store_min = pyo.Constraint(m.T, rule=lambda m, t: m.store[t] >= -store_cap)
rule = lambda m, t: m.produce[t] + m.store[t] == demand[t] # conservation rule
m.consv = pyo.Constraint(m.T, rule=rule)
# objective
def obj(stack, price, demand, m):
cost = 0
for t in m.T:
load = m.produce[t]
idx = np.searchsorted(stack, m.produce[t])
p = price[idx] if idx < len(price) else 1000 # penalty for exceeding production capability
cost += m.produce[t] * p
return cost
rule = functools.partial(obj, stack, price, demand)
m.objective = pyo.Objective(rule=rule, sense=pyo.minimize)
# more constraints added below ...
The problem seems to be in the objective function definition, using the np.searchsorted algorithm. The specific error is
Cannot create a compound inequality with identical upper and lower
bounds using strict inequalities: constraint infeasible:
produce[0] < produce[0] and 50.0 < produce[0]
If I try to implement my own searchsorted-like algorithm, I get a similar error. I gather the expression for the objective function that Pyomo is trying to create can't deal with this kind of table lookup, at least how I've implemented it. Is there another approach or reformulation I can consider?
There's a lot going on here.
The root cause is a conceptual misunderstanding of how Pyomo works: the rules for forming constraints and objectives are not callback functions that are called during optimization. Instead, the rules are functions that Pyomo calls to generate the model, and those rules are expected to return expression objects. Pyomo then passes those expressions to the underlying solver(s) through one of several standard intermediate formats (e.g., LP, NL, BAR, GMS formats). As a result, as a general rule, you should not have rules that have logic that is conditioned on the value of a variable (the rule can run, but the result will be a function of the initial variable value and will not be updated/changed during the optimization process).
For your specific example, the challenge is that searchsorted is iterating over the m.produce variable and comparing it to the cutpoints. That is causing Pyomo to start generating expression objects (through operator overloading). You are then running afoul of a (deprecated) feature where Pyomo allowed for generating compound (range) inequality expressions with a syntax like "lower <= m.x <= upper".
The solution is that you need to reformulate your objective to return an expression for the objective cost. There are several approaches to doing this, and the "best" approach depends on the balance of the model and the actual shape of the cost curve. From your example, it looks like the cost curve is intended to be piecewise linear, so I would consider either directly reformulating the expression (using an intermediate variable and a set of constraits), or to use Pyomo's "Piecewise" component for generating piecewise linear expressions.
I want that a design variable assumes only specified values during optimization process.
For example:
Let x be the variable which can assume only specific value, e.g.:
x = [0.1,0.5,1.0,1.7,2.3]
How can be written in python using pyomo (if it's possible)?
I hope I was clear.
You have to do this with integer variables. For example, if there are N possible values of x, then let x[n] = 1 if x equals the nth possible value, and 0 otherwise. Any time you have an x in your original model, replace it with
sum {n=1,...,N} v[n] * x[n]
where v[n] is the nth possible value. Finally, add a constraint that says:
sum {n=1,...,N} x[n] == 1
I'm not writing these in Pyomo syntax, but this is a general modeling approach that is the same no matter what modeling language/package you use.
I want to eliminate linear equality constraints on integral variables in a pyomo model by substitution. For instance, I wish to transform the model
by substituting
( * )
to
Is there a way to perfom such a substitution in a pyomo model? I will be able to obtain ( * ) by computing the solution space of the corresponding system of linear diophantine equations in the form y = const_vec + susbtitution_matrix * eta, where in our example we have
const_vec = np.array([1,0,0])
substitution_matrix = np.array([[-1,0],
[1,0],
[0,1]])
What you are describing is generally referred to as "variable aggregation." As you indicate, there are four basic steps:
Identify the linear equality equations you want to remove
Compute the substitution map
Deactivate the equality constraints that you want to remove
Substitute variables on all remaining constraints
It sounds like you have 1 and 2 under control. For 3, assuming you identified a Constraint m.c you want to deactivate, you just need to call m.c.deactivate().
For 4, you will want to generate new expressions for the remaining Constraint "body" expressions (variables only appear in the body and not in the lower/upper bounds). For current Pyomo releases (through 5.4.x), you can perform variable substitution by leveraging the clone_expression(). You need to generate a "substitution map": a dict that maps the id() of the variables you want to the new expression you want to use. For example:
from pyomo.core.base.expr import clone_expression
m = ConcreteModel()
m.y = Var([1,2,3])
m.eta = Var([1,2])
# ...
m.c = Constraint(expr=m.y[1]**2 + m.y[3]**2 <= 4)
# ...
substitution_map = {
id(m.y[1]): 1 - m.eta[1],
id(m.y[2]): m.eta[1],
id(m.y[3]): m.eta[2],
}
m.c = (m.c.lower, clone_expression(m.c.body, substitute=substitution_map), m.c.upper)
Finally, the disclaimers:
Setting the constraint with this syntax should work with recent Pyomo releases (I tested back through 5.1)
This approach technically violates one of the assumptions in the current Pyomo expression system (it generates potentially "entangled" expressions: expressions that share common sub-trees). While not "good", it shouldn't cause troubles, unless you do additional transformations / expression manipulation.
Pyomo 5.5 will have a new expression system that will likely have a different mechanism for manipulating / substituting variables.