I'm creating a larger LP model, that needs to assign a product k from a production facility i to customer j. I'm having trouble with implementing a constraint related to shipping the products in containers.
z_ij is the amount of containers to ship from location i to j.
x_ijk is the amount of product k shipped from i to j.
If a product fills up 1% of a container, I need the model to know that it should assign 2 containers, if we ship 150 products.
I'm implementing a constraint with a x_ijk decision variable. The sum of the decision variable must be multiplied with a factor for each i and for each j (how much of a container the product takes up).
I tried creating a dictionary called container_cap, showing how much of a container a product takes up. Multiplying that with my decision variable should provide the required amount of containers.
#Containers to ship
for i in production:
for j in warehouse:
prob += lpSum([x[i][j][k]] * container_cap[k] for k in product) <= lpSum([z[i][j]])
However, I get a "Can't multiply sequence by non-int of type 'list'"-error.
Any suggestions?
You have a one-character error in the code above. You have written:
prob += lpSum([x[i][j][k]] * container_cap[k] for k in product) <= lpSum([z[i][j]])
In this you are trying to multiply a list of one element [x[i][j][k]] by a potentially non-integer container_cap[k]. This is why you are throwing an error. In python you can duplicate a list n-times over by multiplying it by n. But n has to be an integer.
What I think you wanted to write is:
prob += lpSum([x[i][j][k] * container_cap[k] for k in product]) <= lpSum([z[i][j]])
Here you are taking a sum of a list of items, with one item for each item k in the list product which you've presumably already defined, and then saying that list needs to be less than another value.
Also, unless I've misunderstood your model, the second lpSum is not necessary. You are basically saying that the number of containers scheduled from i to j needs to be enough to fit all the product that has been scheduled from i to j; so I think you want the following:
prob += lpSum([x[i][j][k] * container_cap[k] for k in product]) <= z[i][j]
Related
I am trying to use this conditional sum in Pulp's objective function. For the second lpSum, I am trying to calculate the costs of when we don't have enough chassis' to cover the demand and will need pool chassis' with a higher costs. Of course, I only want to calculate this when we don't have enough dedicated chassis'(dedicated_chassis_needed) to cover the demand(chassis_needed) for each day.
The problem is a cost minimizing one. The last "if" part doesn't seem to be working and the lpSum seems to be summing up every date's pool cost and ignoring the if condition, and it just sets the decision variable of dedicated_chassis_needed to 0(lower constraint) and the objective value is a negative number which should not be allowed.
prob += lpSum(dedicated_chassis_needed * dedicated_rate for date in chassis_needed.keys()) + \
lpSum(((chassis_needed[(date)] - dedicated_chassis_needed) * pool_rate_day) \
for date in chassis_needed.keys() if ((chassis_needed[(date)] - dedicated_chassis_needed) >= 0))
In general, in LP, you cannot use a conditional statement that is dependent on the value of a variable in any of the constraints or objective function because the value of the variable is unknown when the model is built before solving, so you will have to reformulate.
You don't have much information there about what the variables and constants are, so it isn't possible to give good suggestions. However, a well-designed objective function should be able to handle extra cost for excess demand without a condition as the model will select the cheaper items first.
For example, if:
demand[day 5] = 20
and
cheap_units[day 5] = 15 # $100 (availability)
and
reserve units = 100 # $150 (availability from some pool of reserves)
and you have some constraint to meet demand via both of those sources and an objective function like:
min(cost) s.t. cost[day] = cheap_units[day] * 100 + reserve_units * 150
it should work out fine...
I'm working with a pyomo model (mostly written by someone else, to be updated by me) that optimizes electric vehicle charging (ie, how much power will a vehicle import or export at a given timestep). The optimization variable (u) is power, and the objective is to minimize total charging cost given the charging cost at each timestep.
I'm trying to write a new optimization function to limit the number of times that the model will allow each vehicle to export power (ie, to set u < 0). I've written a constraint called max_call_rule that counts the number of times u < 0, and constrains it to be less than a given value (max_calls) for each vehicle. (max_calls is a dictionary with a label for each vehicle paired with an integer value for the number of calls allowed.)
The code is very long, but I've put the core pieces below:
model.u = Var(model.t, model.v, domain=Integers, doc='Power used')
model.max_calls = Param(model.v, initialize = max_calls)
def max_call_rule(model, v):
return len([x for x in [model.u[t, v] for t in model.t] if x < 0]) <= model.max_calls[v]
model.max_call_rule = Constraint(model.v, rule=max_call_rule, doc='Max call rule')
This approach doesn't work--I get the following error when I try to run the code.
ERROR: Rule failed when generating expression for constraint max_call_rule
with index 16: ValueError: Cannot create an InequalityExpression with more
than 3 terms.
ERROR: Constructing component 'max_call_rule' from data=None failed:
ValueError: Cannot create an InequalityExpression with more than 3 terms.
I'm new to working with pyomo and suspect that this error means that I'm trying to do something that fundamentally won't work with an optimization program. So--is there a better way for me to constrain the number of times that my variable u can be less than 0?
If what you're trying to do is minimize the number of times vehicles are exporting power, you can introduce a binary variable that allows/disallows vehicles discharging. You want this variable to be indexed over time and vehicles.
Note that if the rest of your model is LP (linear, without any integer variables), this will turn it into a MIP/MILP. There's a significant difference in terms of computational effort required to solve, and the types of solvers you can use. The larger the problems, the bigger the difference this will make. I'm not sure why u is currently set as Integers, that seems quite strange given it represents power.
model.allowed_to_discharge = Var(model.t, model.v, within=Boolean)
def enforce_vehicle_discharging_logic_rule(model, t, v):
"""
When `allowed_to_discharge[t,v]` is 1,
this constraint doesn't have any effect.
When `allowed_to_discharge[t,v]` is 1, u[t,v] >= 0.
Note that 1e9 is just a "big M", i.e. any big number
that you're sure exceeds the maximum value of `model.u`.
"""
return model.u[t,v] >= 0 - model.allowed_to_discharge[t,v] * 1e9
model.enforce_vehicle_discharging_logic = Constraint(
model.t, model.v, rule=enforce_vehicle_discharging_logic_rule
)
Now that you have the binary variable, you can count the events, and specifically you can assign a cost to such events and add it to your objective function (just in case, you can only have one objective function, so you're just adding a "component" to it, not adding a second objective function).
def objective_rule(model):
return (
... # the same objective function as before
+ sum(model.u[t, v] for t in model.t for v in model.v) * model.cost_of_discharge_event
)
model.objective = Objective(rule=objective_rule)
If what you instead of what you add to your objective function is a cost associated to the total energy discharged by the vehicles (instead of the number of events), you want to introduce two separate variables for charging and discharging - both non-negative, and then define the "net discharge" (which right now you call u) as an Expression which is the difference between discharge and charge.
You can then add a cost component to your objective function that is the sum of all the discharge power, and multiply it by the cost associated with it.
Dear fellow python users,
I am building a multi period multi product planning model using Pulp.
What the model should do is rather simple: plan production against minimal holding and production costs while meeting demand.
I have the following data:
periods = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
products = ['A', 'B', 'C']
And I create the following variables:
prod_vars = pulp.LpVariable.dicts('production', [(i,j) for i in products for j in periods],0)
inv_vars = pulp.LpVariable.dicts('inventory', [(i,j) for i in products for j in periods],0)
There are 2 constraints, 1 to always meet demand and 1 to stay below production capacity.
Please note that there is a dataframe (input_data) that retrieves the value of the given demand for that period.
for i in products:
for j in periods[1:]:
model.addConstraint(pulp.LpConstraint(
e=inv_vars[(i,j-1)] + prod_vars[(i,j)] - inv_vars[(i,j)],
sense=pulp.LpConstraintEQ,
name='inv_balance_' + str(i)+ str(j),
rhs=input_data[i][j-1]))
for j in periods:
model.addConstraint(pulp.LpConstraint(
e=(pulp.lpSum(prod_vars[(i,j)] for i in products)),
sense=pulp.LpConstraintLE,
name='total_production_capacity'+str(j),
rhs=(input_data['production_capacity'][j-1])
Then I add cost function and set the objective:
total_production_cost = production_cost*pulp.lpSum(prod_vars)
total_holding_cost =holding_cost * pulp.lpSum(inv_vars)
objective = total_holding_cost + total_production_cost + activation_cost
model.setObjective(objective)
This model works all fine and gives me an output like this.
prod_vars: (A,1) =5, (B,1)=10, (C,1)=15 and so on for all periods.
However: I want to penalize the system for producing multiple products. I.e., adding fixed costs when choosing to produce a second or third product. It would then be more benefical to produce more of product A and hold inventory for some months then to produce A every month. I tried so by adding another variable:
use_vars = pulp.LpVariable.dicts('uselocation', [(i,j) for i in products for j in periods] , 0,1,pulp.LpBinary)
And add fixed costs for using the variable:
activation_cost = pulp.lpSum(activation_cost*use_vars[(i,j)] for i in products for j in periods)
I think that I would need to multiply al my prod_vars by my use_vars in the two constraints. However, if I would do this in my first inventory constraint, Pulp gives the error that my constraint is not linear anymore.
Does someone know how I can make this happen??
Thanks in advance :)
I want to penalize the system for producing multiple products. I.e.,
adding fixed costs when choosing to produce a second or third product.
It is best to step away from code and look at it mathematically.
Let x(i,t)>=0 be the production of item i in period t
As we need to count, we need binary variables. So, introduce:
y(i,t) = 1 if item i is produced in period t
0 otherwise
Then we can add
x(i,t) <= M*y(i,t) (M large enough constant: i.e. capacity)
This implements y(i,t)=0 => x(i,t)=0. We don't have to worry about the other way around x(i,t)=0 => y(i,t)=0 as that is taken care of by the objective (minimize cost).
To add a special cost for producing 2 or 3 products, we need one more binary variable:
count(k,t) = 1 if the number of products produced is k (k=0,1,2,3)
= 0 otherwise
This can be calculated as:
y(A,t)+y(B,t)+y(C,t) = 1*count(1,t)+2*count(2,t)+3*count(3,t)
count(1,t)+count(2,t)+count(3,t) <= 1
Now you can add say: 100*count(2,t)+200*count(3,t) to your cost calculation. (Note: just for completeness: I assumed the cost for 3 products is at least as large as the cost for 2 products).
To achieve what you intend you can add additional constraints connecting prod_vars and use_vars like (pseudo-code):
prod_vars[(i, j)] >= use_vars[(i, j)] forall i, j
prod_vars[(i, j)] <= M * use_vars[(i, j)] forall i, j
where M can be set to max(input_data['production_capacity']).
Doing this you don't need modify the original constraints.
I'm pretty new to python or rather coding in general and trying to find my way through. I spent days and nights on searching the web and read trough all tips and docs I've found in this regard but couldn't find the solution for my problem.
My aim is actually a pretty basic LP, finding the most suitable amount of volume which is bound to a min and max per index lane and maximizing the profit which is different in each lane. I've found some code which base was pretty close to what I've been looking for and adjusted it according to my needs
prob = pulp.LpProblem('LaneSelectionOptimization', LpMaximize)
'''Set the variable'''
x = LpVariable.dicts('Lane',Lanes,None,None,LpInteger)
for l in Lanes:
x[l].bounds(MinVols[l], MaxVols[l])
''' Set the objective function '''
prob += lpSum([x[l] * Impacts[l] for l in Lanes]), 'Sum_of_Impact'
''' Set the constraints '''
#to meet the requirements of the high level constrains i.e. total optimized volume shouldn't differ more than +/-10%
prob += lpSum([x[l] for l in Lanes]) <= VOLUME_LIMIT_UPPER
prob += lpSum([x[l] for l in Lanes]) >= VOLUME_LIMIT_LOWER
So far all went well and it does what it supposed to.
Now I tried to add another constraint which basically needs to aggregate the variable on a certain column string (SecToSecRel) i.e. creating a subtotal which should be less than the value which is allocated to the string in a different table
This is the part of the second table and the value which is aligned to it, which works.
Total_Customer_Target = pd.DataFrame({"TOrgNo":data2.iloc[:,1],"SecToSecRel":(data2.iloc[:,2]+data2.iloc[:,3]), "Target 2018":data2.iloc[:,6]})
SRGNRel_Customer_Target_lane = (Total_Customer_Target[Total_Customer_Target.SecToSecRel == SecToSecRel[l]].sum()["Target 2018"])*1.10
Then adding the constraint....I tried out various ways and unfortunately didn't keep all of them.
1st Try - Didn't work
prob += lpSum([x[l] for l in Lanes if any(SecToSecRel) == SecToSecRel[l]]) <= SRGNRel_Customer_Target_lane,
2nd Try - Stopped in the middle and returned a KeyError
for s in Total_Customer_Target.SecToSecRel:
prob += lpSum([x[l] for l in Lanes if s in SecToSecRel[l]]) <= SRGNRel_Customer_Target_lane
3rd Try - Thought I had to get rid of the Key error to get it work, setting a default - but didn't work
for s in Total_Customer_Target.SecToSecRel:
default = 'No Sector Relation'
SecToSecRel.append(Total_Customer_Target.setdefault(s,default))
prob += lpSum([x[l] for l in Lanes if s in SecToSecRel[l]]) <= SRGNRel_Customer_Target_lane,
Is there anyone who could help me please ?
this sounds like an interesting problem!
First of all, you can't use if statement when modeling your problem.
Just to be clear, you want the sum of lanes in SecToSecRel to be less than SRGNRel_Customer_Target_lane?
In my optimization problem, I have a conditional that the amount of items (LpInteger) in a particular group may not exceed a percentage of the total amount of items. To do that, I wrote the following code:
total = lpSum([num[i].varValue for i in ind])
for d in length:
# get list of items that satisfy the conditional
items_length_d = list(compress(items,[work[i]==work_group[d] for i in items]))
# use that list to calculate the amount of items in the group (an item can occur multiple times)
amount[d] = lpSum([num[dl] for dl in items_length_d])
max_d[d] = total*perc_max[d] + 1
min_d[d] = total*perc_min[d] - 1
prob += max_d[d] >= amount[d]
prob += min_d[d] <= amount[d]
The problem with this approach is that my maximum and minimum become floats (LpContinuous). This in turn makes the solution infeasible.
How can I make sure that each max_d and min_d values are integers? Preferably, I would also like to round up max_d, while truncating min_d.
Edit
I solved the problem of an infeasible solution by changing total = lpSum([num[i].varValue for i in ind]) to total = lpSum([num[i] for i in ind]). However, the minimum and maximum values are still floats. If someone knows how to convert these to ints, an answer would still be very appreciated.
You appear to misunderstand how constructing and solving an Linear Programming problem works.
The entire problem should be set up, then solved and the solution values extracted.
You can't get the LpVariable.varValue for a variable while setting up the problem.
So for a fractional constraint if we define an the group as i /in G and then define the total as i /in T
we get where f is the required fraction
if rearrange this equation.
so in your code
prob += perc_max[d] * lpSum([num[i] for i in ind]) <= lpSum([num[dl] for dl in items_length_d])