I have a solver in Z3Py to which I've added a number of constraints. Each constraint is an inequality involving variables and/or real numbers. Here's what the first few entries in the solver might look like:
[a + b >= 1.5,
a <= 1.5 + b,
b <= 1.5 + a,
2.27 + c >= 1.41, ... ]
What I would like to do is, given this solver, return a single solution [a = ..., b = ..., c = ..., ...] with a,b,c real numbers with the property that any other solution has a smaller value for one of the variables. Since there may be multiple such "maximum" solutions, I just want the first one found. I'm not that familiar with Z3, so I don't know how to return multiple solutions / a range of solutions for a system of inequalities, how to compare them, etc. How can I do this?
This sort of optimization is known as finding the Pareto front. The elements of the Pareto front have the property that to find another optimal solution for one objective, you have to make some other objective a bit "worse."
Z3 has direct support for this support of optimization. See Section 8.1 of https://theory.stanford.edu/~nikolaj/programmingz3.html
Based on this, here's how you can code your example in z3:
from z3 import *
a, b, c = Reals('a b c')
opt = Optimize()
opt.set(priority='pareto')
opt.add(a + b >= 1.5)
opt.add(a <= 1.5 + b)
opt.add(b <= 1.5 + a)
opt.add(2.27 + c >= 1.41)
ma = opt.maximize(a)
mb = opt.maximize(b)
mc = opt.maximize(c)
while opt.check() == sat:
print ("a =", ma.value(), "b =", mb.value(), "c =", mc.value())
Alas, the optimizer is not very good at handling this program. I ran it for a few minutes but it did not produce any results. Adding extra constraints to further reduce the space might help it converge faster.
Related
I'm new to Pyomo and I need help writing this equation in Pyomo.
I'm trying to write a (ranged inequality) constraint equation in Pyomo.
Here is the equation:
So far I wrote these 2 versions:
Version 1: Not sure if this correct
model.amount_of_energy_con = pe.ConstraintList()
for t in model.time:
lhs = 0
rhs = sum(model.c_ratings[s] * model.boat_capacity * model.charging[b, t, s] * model.boats_availability[b][t] for b in model.boats for s in model.chargers)
body = sum(model.charge_energy[b, t, s] for b in model.boats for s in model.chargers)
model.amount_of_energy_con.add(lhs <= body)
model.amount_of_energy_con.add(body <= rhs)
Version 2: I think this is not correct
model.amount_of_energy_con = pe.ConstraintList()
for t in model.time:
lhs = 0
rhs = sum(model.c_ratings[s] * model.boat_capacity * model.charging[b, t, s] * model.boats_availability[b][t] for b in model.boats for s in model.chargers)
body = sum(model.charge_energy[b, t, s] for b in model.boats for s in model.chargers)
#model.amount_of_energy_con.add(expr=pe.inequality(lhs, body, rhs))
model.amount_of_energy_con.add(lhs, body, rhs)
Note:
All the subscripts in the equation are elements of 3 different sets. s Elements of Set S (model.chargers), b Elements of Set B (model.boats), t Elements of Set T (model.time).
C-rate, Availability, Battery capacity are given parameters while E and Charging are Variables in Pyomo.
Please, let me know what you think and how to write it in Pyomo. Generally if there is something you think I'm doing wrong, please me know and also if you need my full code, data and further explanation let me know as well.
Thank you so much for your help
This solution works for me:
model.amount_of_energy_con = pe.ConstraintList()
for t in model.time:
for b in model.boats:
for s in model.chargers:
lhs = model.charge_energy[b, t, s]
rhs = model.c_rating[s] * model.boat_battery_capacity * boats_availability[b, t] * model.charging[b, t, s]
model.amount_of_energy_con.add(expr= (lhs <= rhs))
The problem with those previous versions I posted above in the question are:
The sum() function/ method will do sum of the variables and parameters which is not what I want because the equation doesn't have summation. The 2 versions above will work if we are trying to do summation of the variables on the right and left hand side separately.
The 0 in the range inequalities/ left hand side was covered using the "within parameter" when writing the charge_energy variable as follows model.charge_energy = pe.Var(model.boats, model.time, model.chargers, within=pe.NonNegativeReals).
Thank you.
I have a geometry problem and I thought of using python to solve it (I have little experience). I need advice on how to approach the problem.
hexagons fitting
In the annex you can see the problem. I have a domain L x B and given the cell size of a hexagon (Ls), I found the maximum number of cells in straight (n) and zig zag (m) directions to cover the domain, staying within it. ErrorL and errorB are the errors that I commit between the boundary of the domain and the cells, since I have an integer number of cells to use, and this error should be minimized.
Here the simple code (maybe is possible to obtain it much simpler, but it works)
from scipy import optimize
import numpy as np
#case cells in L straight (n), in B zig zag (m)
L = 500
B = 200
Ls = 10
Lz = Ls*(3**(0.5)/2)
print('Ls =', Ls)
print('Lz =', Lz)
print('-------------')
def errorL(n):
return abs(L-(Ls*n + Ls/2))
def errorB(m):
return abs(B-(Lz*m + Lz/3))
resultL = optimize.minimize_scalar(errorL)
resultB = optimize.minimize_scalar(errorB)
a = round(resultL.x)
b = round(resultL.x)-1
c = round(resultB.x)
d = round(resultB.x)-1
if (Ls*a + Ls/2) <= L:
print('n =', a)
print('errorL =', errorL(a))
else:
print('n =', b)
print('errorL =', errorL(b))
if (Lz*c + Lz/3) <= B:
print('m =', c)
print('errorB =', errorB(c))
else:
print('m =', d)
print('errorB =', errorB(d))
This was an example with a given cell size Ls, now I would like to find a way to mantain Ls within a range [a,b] and find the best Ls, n and m to minimize errorL and errorB.
Each minimization of errorL and errorB depends on two variables, but the choice of Ls affects both.
I started writing a minimization problem with two variables but I am not sure how to proceed considering errorL and errorB to minimize with the same variable Ls. Furthermore I would like to work only with n and m as integer numbers. How can i do it?
Any suggestions is welcome.
Thank you very much
dr
At constant Ls, you need not to solve an optimization problem in order to fine m and n. You can simply set:
n = floor((L-Ls/2)/Ls)
m = floor((B-Lz/3)/Lz)
and the errors will become:
errorL = L-(Ls*n + Ls/2)
errorB = B-(Lz*m + Lz/3)
and of course Lz is equal to sqrt(3)/2*Ls
So, both error values become functions of only a single variable i.e. Ls. Now you can define a single cost function (for example errorL*errorB or errorL+errorB) and minimize it w.r.t. Ls.
I have a LP problem without any objective i.e. it looks like Ax <= B. Now the feasible set contains potentially infinitely many solutions. Is there any way to enumerate different solutions, which are reasonably different among each other?
For now, I am using this code which chooses a random optimization function, hoping that would produce different solutions.
import gurobipy as gp
import numpy as np
def solve(A, B):
model = gp.Model()
model.Params.OutputFlag = False
x = model.addVars(A.shape[1]).values()
for a, b in zip(A, B):
expr = gp.LinExpr(b)
expr.addTerms(a, x)
model.addConstr(expr <= 0)
expr = gp.LinExpr()
for x_ in x:
if np.random.random() < 0.5:
expr.add(x_)
else:
expr.add(-x_)
model.setObjective(expr, gp.GRB.MAXIMIZE)
model.optimize()
return np.array([x_.x for x_ in x])
n_constr = 6
n_var = 5
A = np.random.random((n_constr, n_var)) * 2 - 1
B = np.random.random((n_constr,)) * 2 - 1
for i in range(3):
print(solve(A, B))
One sample output
[ 1.59465412 0. 0. -0.77579453 0. ]
[-1.42381457 0. 0. -7.70035252 -8.55823707]
[1.8797086 0. 0. 7.24494007 4.43847791]
Is there any elegant Gurobi specific solution?
Your approach is of course a perfectly valid method to sample different solutions. One other possible way is to enumerate all basic feasible solutions (corner points). Not so easy however. There is an interesting trick for this: use binary variables to encode the basis (0=non-basic,1=basic) and then use the Gurobi solution pool to enumerate all feasible integer solutions. That will give you all basic feasible solutions. See link for details.
I think it depends on what you mean by reasonably different. Erwin's suggested approach works well if you want solutions at a gap of 1. If you want solutions with a gap of 0.5, define a dummy variable Z = 2X and set Z as an integer and X as continuous. You can then use the solution pool in Gurobi to enumerate any number of solutions (specified using PoolSolutions parameter Link. Similarly, you can vary the factor k in Z = k X to get solutions at some gap. You don't necessarily have to set any objective when using a solution pool. In that case, do not define the PoolGapparameter.
I'm working on a blending problem similar to the pulp example
I have this constrain to make sure the quantity produced is the desired one
prob += lpSum([KG[i] * deposit_vars[i] for i in deposit]) == 64, "KGRequirement"
But I also need to add another constraint for the minimun value different than zero, this is because is not convenient that I take for example, 0.002KG of one ingredient, I have to take either 0 or at least 2 kg, hence valid cases are e.g. 0, 2, 2.3, 6, 3.23.
I tried to make it this way:
for i in deposit:
prob += (KG[i] * deposit_vars[i] == 0) or (TM[i] * deposit_vars[i] >= 30)
But that is not working and it just make the problem Infeasible
EDIT
This my current code:
import pulp
from pulp import *
import pandas as pd
food = ["f1","f2","f3","f4"]
KG = [10,20,50,80]
Protein = [18,12,16,18]
Grass = [13,14,13,16]
price_per_kg = [15,11,10,22]
## protein,carbohydrates,kg
df = pd.DataFrame({"tkid":food,"KG":KG,"Protein":Protein,"Grass":Grass,"value":price_per_kg})
deposit = df["tkid"].values.tolist()
factor_volumen = 1
costs = dict((k,v) for k,v in zip(df["tkid"],df["value"]))
Protein = dict((k,v) for k,v in zip(df["tkid"],df["Protein"]))
Grass = dict((k,v) for k,v in zip(df["tkid"],df["Grass"]))
KG = dict((k,v) for k,v in zip(df["tkid"],df["KG"]))
prob = LpProblem("The Whiskas Problem", LpMinimize)
deposit_vars = LpVariable.dicts("Ingr",deposit,0)
prob += lpSum([costs[i]*deposit_vars[i] for i in deposit]), "Total Cost of Ingredients per can"
#prob += lpSum([deposit_vars[i] for i in deposit]) == 1.0, "PercentagesSum"
prob += lpSum([Protein[i] *KG[i] * deposit_vars[i] for i in deposit]) >= 17.2*14, "ProteinRequirement"
prob += lpSum([Grass[i] *KG[i] * deposit_vars[i] for i in deposit]) >= 12.8*14, "FatRequirement"
prob += lpSum([KG[i] * deposit_vars[i] for i in deposit]) == 14, "KGRequirement"
prob += lpSum([KG[i] * deposit_vars[i] for i in deposit]) <= 80, "KGRequirement1"
prob.writeLP("WhiskasModel.lp")
prob.solve()
# The status of the solution is printed to the screen
print ("Status:", LpStatus[prob.status])
# Each of the variables is printed with it's resolved optimum value
for v in prob.variables():
print (v.name, "=", v.varValue)
# The optimised objective function value is printed to the screen
print ("Total Cost of Ingredients per can = ", value(prob.objective))
The new contrain I want to add is in this part:
prob += lpSum([KG[i] * deposit_vars[i] for i in deposit]) <= 80, "KGRequirement1"
Where I want the product KG[i] * deposit_vars[i] be either 0 or to be between a and b
In the traditional linear programming formulation, all variables, objective function(s), and constraints need to be continuous. What you are asking is how to make this variable a discrete variable, i.e. it can only accept values a,b,... and not anything in between. When you have a combination of continuous and discrete variables, that is called a mixed integer problem (MIP). See PuLP documentation that reflects this explanation. I suggest you carefully read the blending problems mentions on "integers;" they are scattered about the page. According to PuLP's documentation, it can solve MIP problems by calling external MIP solver, some of which are already included.
Without a minimum working example, it is a little tricky to explain how to implement this. One way to do this would be to specify the variable(s) as an integer with the values it can take as a dict. Leaving the default solver, COIN-OR's CBC solver solver, will then solve the MIP. Meanwhile, here's a couple of resources for you to move forward:
https://www.toptal.com/algorithms/mixed-integer-programming#example-problem-scheduling
Note how it uses CBC solver, which is the default solver, to solve this problem
http://yetanothermathprogrammingconsultant.blogspot.com/2018/08/scheduling-easy-mip.html
A more explicit example on how they set-up their integer variables and call the CBC solver
'or' is not something you can use in an LP / MIP model directly. Remember, an LP/MIP consists of a linear objective and linear constraints.
To model x=0 or x≥L you can use socalled semi-continuous variables. Most advanced solvers support them. I don't believe Pulp supports this however. As a workaround you can also use a binary variable δ:
δ*L ≤ x ≤ δ*U
where U is an upperbound on x. It is easy to see this works:
δ = 0 ⇒ x = 0
δ = 1 ⇒ L ≤ x ≤ U
Semi-continuous variables don't require these constraints. Just tell the solver variable x is semi-continuous with bounds [L,U] (or just L if there is no upperbound).
The constraint
a*x=0 or L ≤ a*x ≤ U
can be rewritten as
δ*L ≤ x*a ≤ δ*U
δ binary variable
This is a fairly standard formulation. Semi-continuous variables are often used in finance (portfolio models) to prevent small allocations.
All of this keeps the model perfectly linear (not quadratic), so one can use a standard MIP solver and a standard LP/MIP modeling tool such as Pulp.
I'm trying to model this problem (for details on it, http://www.mpi-hd.mpg.de/personalhomes/bauke/LABS/index.php)
I've seen that the proven minimum for a sequence of 10 digits is 13. However, my application seems to be getting 12 quite frequently. This implies some kind of error in my program. Is there an obvious error in the way I've modeled those summations in this code?
def evaluate(self):
self.fitness = 10000000000 #horrible practice, I know..
h = 0
for g in range(1, len(self.chromosome) - 1):
c = self.evaluateHelper(g)
h += c**2
self.fitness = h
def evaluateHelper(self, g):
"""
Helper for evaluate function. The c sub g function.
"""
totalSum = 0
for i in range(len(self.chromosome) - g - 1):
product = self.chromosome[i] * self.chromosome[(i + g) % (len(self.chromosome))]
totalSum += product
return totalSum
I can't spot any obvious bug offhand, but you're making things really complicated, so maybe a bug's lurking and hiding somewhere. What about
def evaluateHelper(self, g):
return sum(a*b for a, b in zip(self.chromosome, self.chomosome[g:]))
this should return the same values you're computing in that subtle loop (where I think the % len... part is provably redundant). Similarly, the evaluate method seems ripe for a similar 1-liner. But, anyway...
There's a potential off-by-one issue: the formulas in the article you point to are summing for g from 1 to N-1 included -- you're using range(1, len(...)-1), whereby N-1 is excluded. Could that be the root of the problem you observe?
Your bug was here:
for i in range(len(self.chromosome) - g - 1):
The maximum value for i will be len(self.chromosome) - g - 2, because range is exclusive. Thus, you don't consider the last pair. It's basically the same as your other bug, just in a different place.