I am new to Pyomo and python and am currently trying to build a MILP model. It'd be great if someone can help me out on this.
So in my production model, I have packing families,pf, and a packing line,l, can handle a packing family at a time. I also have a set of products, p.
In my model, capacity is reserved on a family level, so I want to construct sets in such a way that pf1 in set pf includes product p1,p2,p3,p4 and pf2 includes p5,p6,p7,p8. For instance, a constraint for implementing packing capacity restriction would be:
model.ct6rePackCap = ConstraintList()
for pf in model.PF for l in model.L for t in model.T for s in model.S for n in model.N:
lhs = sum(r[p,l,t,s,n] for p in model.P)
rhs = RPack[l,pf,t,s]
model.ct6reCap.add (lhs <= rhs)
Here r is the quantity of product with product index p and RPack is the capacity reserved for the packing family pf, to which each p belongs. How can I connect p and pf here such that each element of pf (pf1, pf2..) contains a set of products e.g. (pf1 = (p1,p2,p3,p4), pf2 =(p5,p6,p7,p8)) ?
I read pyomo document and read something about subset but it didn't seem like it would achieve what I want.
Thanks a lot in advance!
Related
I can't manage to find a way to create a constraint that sounds like this: for example I have 2 variables, one is a regular product and the other one is a super rare product. In order to have a super rare product, you will need to have already 25 of the regular version of that product. This can be stackable (e.g. if the algorithm select 75 of that regular product, it can have 3 super rare). The reason for this is that the super rare is more profitable, so if I place it without any constraints, it will select only the super rare ones. Any ideas on how to write such a constraint?
Thanks in advance!
Part of the code:
hwProblem = LpProblem("HotWheels", LpMaximize)
# Variables
jImportsW_blister = LpVariable("HW J-Imports w/ blister", lowBound=20, cat=LpInteger) # regular product
jImportsTH = LpVariable("HW J-Imports treasure hunt", lowBound=None, cat=LpInteger) # super rare product
# Objective Function
hwProblem += 19 * jImportsW_blister + 350 * jImportsTH # profit for each type of product
# Constraints
hwProblem += jImportsW_blister <= 50, "HW J-Imports maximum no. of products"
hwProblem += jImportsTH <= jImportsW_blister / 25
# ^this is where the error is happening
There's a few "missing pieces" here regarding the structure of your model, but in general, you can limit the "super rare" (SR) by doing something like:
prob += SR <= R / 25
From Google's OR-Tools Linear Solver tutorial this block of code is referenced.
foods = [solver.NumVar(0.0, solver.infinity(), item[0]) for item in data]
# Create the constraints, one per nutrient.
constraints = []
for i, nutrient in enumerate(nutrients):
constraints.append(solver.Constraint(nutrient[1], solver.infinity()))
for j, item in enumerate(data):
constraints[i].SetCoefficient(foods[j], item[i + 3])
print('Number of constraints =', solver.NumConstraints())
Solver.Constraint appears to accept a lower bound and upper bound arguments. However, when this constraint is later referenced in the nested for loop and the method, .SetCoefficient is referenced, foods[j] retrieves from list, foods, a NumVar object and item[i+3] retrieves the nutrient i of food item j. But beyond here, I can't make sense of what SetCoefficient is doing and how this functions as a linear constraint.
The source code provides the following guidance:
class Constraint(object):
r"""
The class for constraints of a Mathematical Programming (MP) model.
A constraint is represented as a linear equation or inequality.
"""
thisown = property(lambda x: x.this.own(), lambda x, v: x.this.own(v), doc="The membership flag")
def __init__(self, *args, **kwargs):
raise AttributeError("No constructor defined")
__repr__ = _swig_repr
...
def SetCoefficient(self, var: "Variable", coeff: "double") -> "void":
r"""
Sets the coefficient of the variable on the constraint.
If the variable does not belong to the solver, the function just returns,
or crashes in non-opt mode.
"""
return _pywraplp.Constraint_SetCoefficient(self, var, coeff)
Define the constraints
The constraints for Stigler diet require the total amount of the nutrients provided by all foods to be at least the minimum requirement for each nutrient. Next, we write these constraints as inequalities involving the arrays data and nutrients, and the variables food[i].
First, the amount of nutrient i provided by food j per dollar is data[j][i+3] (we add 3 to the column index because the nutrient data begins in the fourth column of data.) Since the amount of money to be spent on food j is food[j], the amount of nutrient i provided by food j is ( data[j][i+3] \cdot food[j] ). Finally, since the minimum requirement for nutrient i is nutrientsi, we can write constraint i as follows: [ \sum_{j} data[j][i+3] \cdot food[j] \geq nutrientsi ;;;;; (1) ] The following code defines these constraints.
I don't understand the syntax of these constraints. A typical linear constraint might take the form of x + 2*y <= 3; however, the syntax here doesn't function that way. How should I use OR-Tools library to define constraints in this more conventional way?
Likewise, what is the usage of SetCoefficient doing and how does it establish a constraint in the form of an inequality?
I am completely new to fipy. I am trying to solve the following set of pdes across two different domains using fipy. The variables are p,n and ψ and q,Dn,Dp,un,up,e and N are all constants. N and e change values from domain 1 to domain 2. Domain 2 is a rectangular domain stacked above a similar rectangular domain 1. The variables p and n are to be solved for in domain 1 and ψ is to be solved for in both the domains- domain1 and domain 2.
qDn∇2n − qun∇.(n∇ψ) = q(n-10**11)/10**(-6), in Domain 1
qDp∇2p + qup∇.(p∇ψ) = -q(p-10**21)/10**(-6), in Domain 1
∇2ψ = −(p − n- N)/e in Domain 1
e∇2ψ = 0 in Domain 2
The code that I have written for solving the pdes has been attached below.
! pip install pyparse
from fipy import *
L= 10**(-6)
h= 20**(-6)
tox= 0.1*10**(-6)
q=1.6*10**(-19)
un=0.14
up=0.045
Vth=0.026
Dp= up*Vth
Dn=un*Vth
p0= 10**(21)
n0= 10**(11)
e0=8.854*10**(-12)
mesh1= Grid2D(dx= L/100,nx=100,dy=h/200,ny=200)
mesh2= Grid2D(dx= L/100,nx=100,dy=tox/10,ny=10)
mesh3= mesh1+(mesh2+[[0],[h]]) # final mesh
x,y= mesh3.cellCenters
N= 10**21*(y<=h) # N changes from Domain 1 to Domain 2
e= 11.9*e0*(y<=h)+ 3.9*e0*(y>h) # e changes from Domain 1 to Domain 2
p1=CellVariable(name='hole',mesh=mesh3,hasOld=True,value=p0)
n1=CellVariable(name='electron',mesh=mesh3,hasOld=True,value=n0)
psi=CellVariable(name='potential',mesh=mesh3,hasOld=True,value=1)
p=p1*(y<=h) # for domain separation
n=n1*(y<=h) # for domain separation
mask1=((y==0))
mask2=(y==h)
mask3=(y==(h+tox))
# 1e50 is the large value
# boundary conditions are p(x,0)= p0, n(x,0)= n0, psi(x,0)= 0, p(x,h)= p0*exp(-psi/Vth), n(x,h)= n0*exp(psi/Vth), psi(h+tox)= 5
eq1=(DiffusionTerm(coeff=q*Dn,var=n)-ConvectionTerm(coeff=q*un*psi.faceGrad,var=n)==ImplicitSourceTerm(coeff=q*(n-10**5)/10**(-6),var=n)-ImplicitSourceTerm(mask1*1e50)-ImplicitSourceTerm(mask2*1e50)+mask1*1e50*n0+mask2*1e50*n0*numerix.exp(psi/Vth))
eq2=(DiffusionTerm(coeff=q*Dp,var=p)+ConvectionTerm(coeff=q*up*psi.faceGrad,var=p)==ImplicitSourceTerm(coeff=-q*(p-10**15)/10**(-6),var=p)-ImplicitSourceTerm(mask1*1e50)-ImplicitSourceTerm(mask2*1e50)+mask1*1e50*p0+mask2*1e50*p0*numerix.exp(psi/Vth))
eq3=(DiffusionTerm(coeff=e,var=psi)==ImplicitSourceTerm(coeff=-q*(p-n-N),var=psi)-ImplicitSourceTerm(mask1*1e50)-ImplicitSourceTerm(mask2*1e50)-ImplicitSouceTerm(mask3*1e50)+mask1*1e50*0+mask2*1e50*psi+mask3*1e50*5)
eq= eq1 & eq2 & eq3
for t in range (50):
p.updateOld()
n.updateOld()
psi.updateOld()
eq.solve(dt=10) # Since the equation does not have any transient term, a large value of dt=5 has been chosen
Now, I am getting the following error: ExplicitVariableError: Terms with explicit Variables cannot mix with Terms with implicit Variables.
I am also not sure whether the coupling between the equations actually work out considering the way I have written the code.
Please note that I am actually solving a MOSCAP using the above pdes.
Any help regarding this would be highly appreciated.
FiPy does not support this usage. One set of equations govern one set of variables defined on one domain. n, p, and psi must all exist on the same mesh in order to be able to write eq3.
Define all equations on mesh3.
Use an internal constraint on n and p.
You will not be able to use mesh1.facesTop and mesh1.facesBottom. Define the internal boundary parametrically.
Note: e should be moved to the diffusion coefficient in eq3. This is both physically correct (divergence of the electric displacement field goes like the charge) and necessary to account for the step in permittivity at the boundary between your subdomains.
I have a list which consist of 2 attributes which are cost and rating. I need to find possible best flights which have lower cost and a higher rating. This is a multi objecting optimization problem with minimizing and maximizing objectives. How can I implement this in DEAP?
I'm struggling to implement Individual since im very new to DEAP.
# Attribute generator
toolbox.register("cost", random.randrange, NBR_ITEMS)
toolbox.register("rating", random.randrange, NBR_ITEMS)
# Structure initializers
toolbox.register("individual", tools.initRepeat, creator.Individual, toolbox.cost, toolbox.rating) #
toolbox.register("population", tools.initRepeat, list, toolbox.individual)
Maybe you could try to encode the cost and the rating using binary numbers.
For example, let's assume the most expensive plane ticket you can get is $16384, you could store that in 14 bits (2^14 = 16384) and the rating is a number from 0 to 10 so you can store that in 4 bits so in total you can store your individuals using 18 bits.
Now you need a function to decode it:
def decode_individual(individual):
decoded_individual = ['', '']
# Decode cost (14 bits)
for i in range(0, 14):
decoded_individual[0] += str(individual[i])
# Decode rating (4 bits)
for i in range(1, 3):
decoded_individual[0] += str(individual[13 + i])
return tuple(map(lambda x: int(x, 2), decoded_individual))
You need to set up your fitness functions for a multi-objective problem, i.e. you need to provide some weights for each function that is positive if you are trying to maximize the function and negative if you are trying to minimize it. In your case, I guess you are trying to maximize de rating and minimize the cost so you could set it up as follows:
creator.create('Fitness', base.Fitness, weights=(1.0, -0.5,))
creator.create('Individual', list, fitness=creator.Fitness)
Your fitness methods should return the result of the functions you are trying to maximize and minimize in the same order as specified in the weights:
def function_cost(individual):
decoded_individual = decode_individual(individual)
return decoded_individual[0]
def function_rating(individual):
decoded_individual = decode_individual(individual)
return decoded_individual[1]
def fitness(individual):
return (function_cost(individual), function_rating(individual)),
Then, instead of registering 2 fitness functions like in your example, register just one:
toolbox.register('evaluate', fitness)
Configure DEAP to use binary data:
toolbox.register('attrBinary', random.randint, 0, 1)
toolbox.register('individual', tools.initRepeat, creator.Individual, toolbox.attrBinary, n=18) # Here you need to specify the number of bits you are using
toolbox.register('population', tools.initRepeat, list, toolbox.individual)
# Register the evaluation function (was named fitness in this example)
toolbox.register('evaluate', fitness)
# Configure your mate and mutation methods, e.g.
toolbox.register('mate', tools.cxTwoPoint)
toolbox.register('mutate', tools.mutFlipBit, indpb=0.15)
Your selection method must support multi-objective problems, NSGA2 as pointed out in your question can be used:
toolbox.register('select', tools.selNSGA2)
Then run the algorithm, you can try different values for the number of individuals (population) the number of generations and the ratings of mating and mutation:
num_pop = 50
num_gen = 100
cx_prob = 0.7
mut_prob = 0.2
best = []
for gen in range(num_gen):
offspring = algorithms.varAnd(population, toolbox, cxpb=cx_prob, mutpb=mut_prob)
fits = toolbox.map(toolbox.evaluate, offspring)
for fit, ind in zip(fits, offspring):
ind.fitness.values = fit
population = toolbox.select(offspring, k=len(population))
top = tools.selBest(population, k=1)
fitness = fitness(top[0])
print(gen, fitness, decode_individual(top[0]), top[0])
best.append(fitness[0])
You may also want to display the best individuals of each generation in a graph:
x = list(range(num_gen))
plt.plot(x, best)
plt.title("Best ticket - Cost / Rating")
plt.show()
Haven't tested this myself and I got largely inspired by some exercise I did at University so hopefully it will work for you.
I am working on a code to solve for the optimum combination of diameter size of number of pipelines. The objective function is to find the least sum of pressure drops in six pipelines.
As I have 15 choices of discrete diameter sizes which are [2,4,6,8,12,16,20,24,30,36,40,42,50,60,80] that can be used for any of the six pipelines that I have in the system, the list of possible solutions becomes 15^6 which is equal to 11,390,625
To solve the problem, I am using Mixed-Integer Linear Programming using Pulp package. I am able to find the solution for the combination of same diameters (e.g. [2,2,2,2,2,2] or [4,4,4,4,4,4]) but what I need is to go through all combinations (e.g. [2,4,2,2,4,2] or [4,2,4,2,4,2] to find the minimum. I attempted to do this but the process is taking a very long time to go through all combinations. Is there a faster way to do this ?
Note that I cannot calculate the pressure drop for each pipeline as the choice of diameter will affect the total pressure drop in the system. Therefore, at anytime, I need to calculate the pressure drop of each combination in the system.
I also need to constraint the problem such that the rate/cross section of pipeline area > 2.
Your help is much appreciated.
The first attempt for my code is the following:
from pulp import *
import random
import itertools
import numpy
rate = 5000
numberOfPipelines = 15
def pressure(diameter):
diameterList = numpy.tile(diameter,numberOfPipelines)
pressure = 0.0
for pipeline in range(numberOfPipelines):
pressure += rate/diameterList[pipeline]
return pressure
diameterList = [2,4,6,8,12,16,20,24,30,36,40,42,50,60,80]
pipelineIds = range(0,numberOfPipelines)
pipelinePressures = {}
for diameter in diameterList:
pressures = []
for pipeline in range(numberOfPipelines):
pressures.append(pressure(diameter))
pressureList = dict(zip(pipelineIds,pressures))
pipelinePressures[diameter] = pressureList
print 'pipepressure', pipelinePressures
prob = LpProblem("Warehouse Allocation",LpMinimize)
use_diameter = LpVariable.dicts("UseDiameter", diameterList, cat=LpBinary)
use_pipeline = LpVariable.dicts("UsePipeline", [(i,j) for i in pipelineIds for j in diameterList], cat = LpBinary)
## Objective Function:
prob += lpSum(pipelinePressures[j][i] * use_pipeline[(i,j)] for i in pipelineIds for j in diameterList)
## At least each pipeline must be connected to a diameter:
for i in pipelineIds:
prob += lpSum(use_pipeline[(i,j)] for j in diameterList) ==1
## The diameter is activiated if at least one pipelines is assigned to it:
for j in diameterList:
for i in pipelineIds:
prob += use_diameter[j] >= lpSum(use_pipeline[(i,j)])
## run the solution
prob.solve()
print("Status:", LpStatus[prob.status])
for i in diameterList:
if use_diameter[i].varValue> pressureTest:
print("Diameter Size",i)
for v in prob.variables():
print(v.name,"=",v.varValue)
This what I did for the combination part which took really long time.
xList = np.array(list(itertools.product(diameterList,repeat = numberOfPipelines)))
print len(xList)
for combination in xList:
pressures = []
for pipeline in range(numberOfPipelines):
pressures.append(pressure(combination))
pressureList = dict(zip(pipelineIds,pressures))
pipelinePressures[combination] = pressureList
print 'pipelinePressures',pipelinePressures
I would iterate through all combinations, I think you would run into memory problems otherwise trying to model ALL combinations in a MIP.
If you iterate through the problems perhaps using the multiprocessing library to use all cores, it shouldn't take long just remember only to hold information on the best combination so far, and not to try and generate all combinations at once and then evaluate them.
If the problem gets bigger you should consider Dynamic Programming Algorithms or use pulp with column generation.