Adding Logical Constraints in PuLP - python

I am trying to add alloys to steel in order to bring carbon content of steel to a certain range, at minimal cost.
But one limitation is that in real life, the machine can only add a minimum of 50kgs of an alloy. So if we are adding a certain alloy, then it can be either 50/60/70 kgs etc. or 0kgs if we are not adding that particular alloy. How would I add a constraint for the same?
Thanks in advance!
Below is the function I've written:
def optimizer_pd(test):
# declare problem
prob = LpProblem("Minimize Alloy Cost",LpMinimize)
# percentage of carbon in each alloy
percs = ele_percs['carbon']
# alloy_vars is a list of all possible alloys
# constraints
prob += lpSum([percs[i] * alloy_vars[i] for i in alloys]) >= minimum_carbon
prob += lpSum([percs[i] * alloy_vars[i] for i in alloys]) <= maximum_carbon
# objective function
prob += lpSum([costs[i] * alloy_vars[i] for i in alloys])
# solving
sol = prob.solve()
# results
var_dict = {}
for var in prob.variables():
var_dict[var.name] = var.value()
return var_dict

Welcome to the site.
In the future, you'll get better answers if you present a minimum reproducible example for what you are trying to do. But, it is clear enough from what you post.
So, you will need to introduce an extra helper or "indicator" binary variable, indexed by your alloys to do this. This yes/no variable indicates the commitment to use at least the minimum amount of the alloy. (You essentially need to break your requirement into 2 variables....)
Then you will need to use a "big M" constraint on the amount to use (or just use the max value). In pseudocode:
use[alloy] ∈ {0,1}
amount[alloy] ∈ non-negative reals
min[alloy], max[alloy] are fixed min/max parameters
Minimum use constraint:
amount[alloy] >= use[alloy] * min[alloy] for each alloy
Maximum use constraint:
amount[alloy] <= use[alloy] * max[alloy] (or big M) for each alloy
Plug in a few numbers to ensure you "believe it" :)

Related

PuLP Conditional Sum based on key of the loop

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...

How to constrain optimization based on number of negative values of variable in pyomo

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.

How to calculate the shadow price in Gurobi

I want to analyze whether the boundary should increase or reduce in Constraints in a programming problem:
The following is simplified problem. V[(i,t)]is decision variable and S[i] is input. I want to know if the obj increases or reduces when increasing one unit of S[i]`.
I know may the shadow price and marginal cost are for decision variable not inputs. In Gurobi, Dual value (also known as the shadow price) can use the Pi function.
for t in range(T):
for i in range(I):
m.addConstr(V[(i,t)] <= Lambda*S[i])
m.addConstr(other constrints without S[i])
obj =cf*quicksum(V[(i,0)] for i in range(I))+ cs*quicksum(S[i]for i in range(I))+...
m.setObjective(obj, GRB.MAXIMIZE)
m.optimize()
There are two ways to get the shadow price:(Python + Gurobi):
shadow_price = model.getAttr('Pi', model.getConstrs())
or
shadow_price = model.getAttr(GRB.Attr.Pi)
It returns the shadow prices of all constraints in sequence into an array.

PulP: Adding constraint which aggregates the variable

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?

PuLP: casting LpVariable or LpAffineExpression to integer

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])

Categories

Resources