Pyomo and Gurobi: Does Pyomo support solver callbacks to Gurobi? - pyomo

I have started using Pyomo for modelling of MILPs and need to add problem specific cutting planes at certain MILP-feasible solutions. I know that it is possible to do that via callbacks in Gurobi's own gurobipy API. However, since I am using Pyomo atm I would like to stick to it if possible. I have seen that there exist persistent/direct solver IO options, however, I could not figure out how to make use of those options for my purposes.
Any help is appreciated.

Callbacks and lazy constraints are currently supported by Pyomo for the Gurobi Persistent solver interface. Here is a small example from the documentation of that interface:
from gurobipy import GRB
import pyomo.environ as pe
from pyomo.core.expr.taylor_series import taylor_series_expansion
m = pe.ConcreteModel()
m.x = pe.Var(bounds = (0, 4))
m.y = pe.Var(within = pe.Integers, bounds = (0, None))
m.obj = pe.Objective(expr = 2 * m.x + m.y)
m.cons = pe.ConstraintList() # for the cutting planes
def _add_cut(xval):
# a function to generate the cut
m.x.value = xval
return m.cons.add(m.y >= taylor_series_expansion((m.x - 2) ** 2))
_add_cut(0) # start with 2 cuts at the bounds of x
_add_cut(4) # this is an arbitrary choice
opt = pe.SolverFactory('gurobi_persistent')
opt.set_instance(m)
opt.set_gurobi_param('PreCrush', 1)
opt.set_gurobi_param('LazyConstraints', 1)
def my_callback(cb_m, cb_opt, cb_where):
if cb_where == GRB.Callback.MIPSOL:
cb_opt.cbGetSolution(vars = [m.x, m.y])
if m.y.value < (m.x.value - 2) ** 2 - 1e-6:
print('adding cut')
cb_opt.cbLazy(_add_cut(m.x.value))
opt.set_callback(my_callback)
opt.solve()
assert abs(m.x.value - 1) <= 1e-6
assert abs(m.y.value - 1) <= 1e-6
Changed value of parameter PreCrush to 1
Prev: 0 Min: 0 Max: 1 Default: 0
Changed value of parameter LazyConstraints to 1
Prev: 0 Min: 0 Max: 1 Default: 0
adding cut
adding cut
adding cut
adding cut
adding cut
adding cut
adding cut
adding cut
For further details on the methods available within this Pyomo callback interface, see the GurobiPersistent class.

Related

How to Formulate a Piecewise Step Function in pyomo

I have a question regarding the correct formulation of a piecewise step function in pyomo. I want to include in my model a single piecewise function of the form:
/ 1 , 0 <= X(t) <= 1
Z(X) = \ 0 , 1 <= X(t) <= 2
Where X is being fit to data over taken over a time domain and Z acts like a binary variable. The most similar example in pyomo documentation is the step.py example using INC. However, when solving with this formulation I observe the problem of the domain variable x ‘sticking’ to the breakpoint at x=1. I assume this is because (as noted in the documentation) Z can solve to the entire vertical line if continuous or is doubly feasible at both 0 and 1 if binary. Other formulations offered via the piecewise function (i.e. dlog, dcc, log, etc.) experience similar issues (in fact, based on the output to GAMS I’m pretty sure they don’t support binary/integer variables at all).
Is there a ‘correct’ way to formulate a piecewise function in pyomo that avoids the multiple-feasibility issue at the breakpoint, thus avoiding the domain variable converging to the breakpoint? I am using BARON with solvers cplex and ipopt, however my gut tells me this formulation issue can’t be solved by simply changing solvers.
I can also send a document illustrating my observations on why the current pyomo piecewise formulations don’t support binary variables, if it would help.
Here's some sample code where we try to minimise the sum of the step function Z.
model = ConcreteModel()
model.A = Set(initialize=[1,2,3])
model.B = Set(initialize=['J', 'K'])
model.x = Var(model.A, model.B, bounds=(0, 2))
model.z = Var(model.A, model.B, domain = Binary)
DOMAIN_PTS = [0,1,1,2]
RANGE_PTS = [1,1,0,0]
model.z_constraint = Piecewise(
model.A, model.B,
model.z, model.x,
pw_pts=DOMAIN_PTS,
pw_repn='INC',
pw_constr_type = 'EQ',
f_rule = RANGE_PTS,
unbounded_domain_var = True)
def objective_rule(model):
return sum(model.z[a,b] for a in model.A for b in model.B)
model.objective = Objective(rule = objective_rule, sense=minimize)
If you set sense = minimize above, the program will solve and give x = 1 for each index value. If you set sense = maximize, the program will solve and give x = 0 for each index value. I'm not too sure what you mean by stickiness, but I don't think this program does it. and it implements the step function.
This assumes that your z is not also indexed by time. If so, I would need to edit this answer:
model.t = RangeSet(*time*)
model.x = Var(model.t, bounds=(0, 2))
model.z = Var(domain=Binary)
model.d = Disjunction(expr=[
[0 <= model.x[t] for t in model.t] + [model.x[t] <= 1 for t in model.t],
[1 <= model.x[t] for t in model.t] + [model.x[t] <= 2 for t in model.t]
])
TransformationFactory('gdp.bigm').apply_to(model)
SolverFactory('baron').solve(model)

"IndexError: Index X is out of bounds" generated when converting 3.X python script to 2.7

I have a script I wrote for 3.X that runs great, however I needed to convert it to 2.7 and while doing so came across this error I don't know how to solve.
This function corrects data by dividing it into periods and finding a percentile for each period to apply to said data.
import numpy
import math
import random
from bokeh.plotting import *
from bokeh.layouts import *
from __future__ import division
def rs_percent_corr(start, end, rs, rso, thresh, period):
num_periods = int(math.ceil((end - start) / period))
rs_period = numpy.zeros(period)
rso_period = numpy.zeros(period)
period_corr = numpy.zeros(num_periods)
# Placing intervals in separate array for easy handling
rs_interval = numpy.array(rs[start:end])
rso_interval = numpy.array(rso[start:end])
# separate the interval into predefined periods and compute correction
count_one = 0 # index for full correction interval
count_two = 0 # index for within each period
count_three = 0 # index for number of periods
while count_one < len(rs_interval):
if (count_two < period) and count_one == len(rs_interval) - 1:
# if statement handles final period
rs_period[count_two] = rs_interval[count_one]
rso_period[count_two] = rso_interval[count_one]
count_one += 1
count_two += 1
while count_two < period:
# This fills out the rest of the final period with NaNs so
# they are not impacted by the remaining zeros
rs_period[count_two] = numpy.nan
rso_period[count_two] = numpy.nan
count_two += 1
ratio = numpy.divide(rs_period, rso_period)
period_corr[count_three] = numpy.nanpercentile(ratio, thresh)
elif count_two < period:
# haven't run out of data points, and period still hasn't been filled
rs_period[count_two] = rs_interval[count_one]
rso_period[count_two] = rso_interval[count_one]
count_one += 1
count_two += 1
else:
# end of a period
count_two = 0
ratio = numpy.divide(rs_period, rso_period)
period_corr[count_three] = numpy.nanpercentile(ratio, thresh)
count_three += 1
return period_corr
When running the script in 3.X it works, but trying to run it in 2.7 generates "IndexError: Index 110 is out of bounds for axis 0 with size 110" on the line period_corr[count_three] = numpy.nanpercentile(ratio, thresh)
What am I missing? Thank you in advance for your time.
The culprit is the line num_periods = int(math.ceil((end - start) / period)).
In Python 3 / is true division. 3 / 2 will return 1.5. In Python 2 this is not the case, / performs integer division so 3/2 will return 1.
If you need to support both versions in the same time, you can insert from __future__ import division in the first line of your script, then Python 2 / will behave like Python 3's.

How to generate a different set of random numbers in each iteration of a prallelized For loop?

The following problem arised directly due to applying the answer to this question.
In the minimal working example (MWE) there's a place in the myscript definition where I generate some random numbers, then perform some operations on them, and fnally write the output to a file. When this code is un-parallelized, it works correct. However, if it's parallel (I'm testing it on a 2-core machine, and have two threads at a time), when I want to perform 4 iterations (boot) I get twice the same output (i.e., among four outputs I get only two distinct numbers, not four as expected). How can this be fixed?
MWE:
import random
import math
import numpy as np
import multiprocessing as mp
from multiprocessing import Pool
boot = 4
RRpoints = 278
def myscript(iteration_number):
RRfile_name = "outputRR%d.txt" % iteration_number
with open(RRfile_name, "w") as RRf:
col1 = np.random.uniform(0 , 1 , RRpoints)
col2 = np.random.uniform(0 , 1 , RRpoints)
sph1 = [i * 2 * math.pi for i in col1]
sph2 = [math.asin(2 * i - 1) for i in col2]
for k in xrange(0 , RRpoints):
h = 0
mltp = sph1[k] * sph2[k]
h += mltp
RRf.write("%s\n" % h)
x = xrange(boot)
p = mp.Pool()
y = p.imap(myscript, x)
list(y)

how to solve 3 nonlinear equations in python

I have the following system of 3 nonlinear equations that I need to solve:
-xyt + HF = 0
-2xzt + 4yzt - xyt + 4z^2t - M1F = 0
-2xt + 2yt + 4zt - 1 = 0
where x, HF, and M1F are known parameters. Therefore, y,z, and t are the parameters to be calculated.
Attemp to solve the problem:
def equations(p):
y,z,t = p
f1 = -x*y*t + HF
f2 = -2*x*z*t + 4*y*z*t - x*y*t + 4*t*z**2 - M1F
f3 = -2*x*t + 2*y*t + 4*z*t - 1
return (f1,f2,f3)
y,z,t = fsolve(equations)
print equations((y,z,t))
But the thing is that if I want to use scipy.optimize.fsolve then I should input an initial guess. In my case, I do not have any initial conditions.
Is there another way to solve 3 nonlinear equations with 3 unknowns in python?
Edit:
It turned out that I have a condition! The condition is that HF > M1F, HF > 0, and M1F > 0.
#Christian, I don't think the equation system can be linearize easily, unlike the post you suggested.
Powell's Hybrid method (optimize.fsolve()) is quite sensitive to initial conditions, so it is very useful if you can come up with a good initial parameter guess. In the following example, we firstly minimize the sum-of-squares of all three equations using Nelder-Mead method (optimize.fmin(), for small problem like OP, this is probably already enough). The resulting parameter vector is then used as the initial guess for optimize.fsolve() to get the final result.
>>> from numpy import *
>>> from scipy import stats
>>> from scipy import optimize
>>> HF, M1F, x=1000.,900.,10.
>>> def f(p):
return abs(sum(array(equations(p))**2)-0)
>>> optimize.fmin(f, (1.,1.,1.))
Optimization terminated successfully.
Current function value: 0.000000
Iterations: 131
Function evaluations: 239
array([ -8.95023217, 9.45274653, -11.1728963 ])
>>> optimize.fsolve(equations, (-8.95023217, 9.45274653, -11.1728963))
array([ -8.95022376, 9.45273632, -11.17290503])
>>> pr=optimize.fsolve(equations, (-8.95023217, 9.45274653, -11.1728963))
>>> equations(pr)
(-7.9580786405131221e-13, -1.2732925824820995e-10, -5.6843418860808015e-14)
The result is pretty good.

initial guess using scipy.optimize in python

I have the following problem to code using python:
I have 7 parameters: x, y, z, t, HF, M1F, and M2F. The user should input any of these 3 and the program should calculate the rest.
The relations that I have are:
HF = -xyt
M1F = -2xzt + 4yzt - xyt + 4tz^2
M2F = 2yzt - xyt
1 = -2xt + 2yt + 4zt
Attempt to solve the problem:
I have 7 parameters and the user should input 3 => I will be left with 4 parameters. So it's all about solving a system of 4 nonlinear equations with 4 unknowns.
I read online that scipy.optimize could be used to solve a system of nonlinear equations.
But I need an initial guess.
Going back to the physics of the problem I have the following initial conditions:
x > 0
y > 0
z < 0
HF > 0
M1F > 0
M2F > 0
M2F > M1F (solving this inequality from the above equations I get: -x + y + 2z < 0)
HF > M1F + d (solving this inequality from the above equations I get: -x + 2y + 2z < 0)
How can these initial conditions help me get the initial guess so that I can solve my problem using scipy.optimize?
I'm not sure optimization is the right way to go here. I think personally I'd start with the three variables given and algebraically solve the rest. There are a lot of combinations, but all things considered the analytic solution is usually best if it's obtainable.