Search code examples
pythonlinear-programmingpulp

Python PuLP Set Constraint on Text/Categorical Field


I am using PuLP to solve a minimization problem for a Loan optimisation problem. The code below is outputting the correct combination of loans with the current constraints (capital requirement and max drawdown amounts).

from pulp import *

class Loan():

def __init__(self, id, interest_rate, drawdown_amount, lender, min_drawdown, max_drawdown):
    self.id = id
    self.interest_rate = interest_rate
    self.drawdown_amount = drawdown_amount
    self.lender = lender
    self.min_drawdown = min_drawdown
    self.max_drawdown = max_drawdown

def __str__(self):
    return f"loan(id={self.id}, lender={self.lender}, drawdown_amount={self.drawdown_amount}, interest_rate={self.interest_rate}, min_drawdown={self.min_drawdown}, max_drawdown={self.max_drawdown})" 


# PROBLEM DATA:
    
capital_requirement = 1200000

ids = ["WF_1", "BA_1", "BA_2", "JP_1"] 
interest_rates = [0.05, 0.03, 0.02, 0.04]
drawdown_amounts = [1,1,1,1,1]
lenders = ["Wells Fargo", "Bank of America" , "Bank of America", "JPMorgan"] 
min_drawdowns = [75000, 100000, 300000, 80000]
max_drawdowns = [500000, 500000, 500000, 500000]

loans = [Loan(id, interest_rate, drawdown_amount, lender, min_drawdown, max_drawdown ) for id, interest_rate, drawdown_amount, lender, min_drawdown, max_drawdown in
              zip(ids, interest_rates, drawdown_amounts, lenders, min_drawdowns, max_drawdowns)]


# DECLARE PROBLEM OBJECT
prob = LpProblem("Loan Optimiser", LpMinimize)

# VARIABLES
loanVars = LpVariable.dicts('loans', loans, 0)

# OBJECTIVE
prob += lpSum([loan.interest_rate * loanVars[loan] for loan in loans])

# CONSTRAINTS
# Amount of money to borrow:
prob += lpSum([loan.drawdown_amount * loanVars[loan] for loan in loans]) == capital_requirement 

# If a loan is included, it must be below the maximum drawdown amount of that loan:
for loan in loans:
    prob += loanVars[loan] <= loan.max_drawdown * loanVars_selected[loan]

Sample output with capital requirement of 1.2m:

---------The optimal loans to use for borrowing € 1200000.0 are----------

$200000.0 of JP_1

$500000.0 of BA_1

$500000.0 of BA_2

Total Interest Cost = $33000.00 Total Interest Rate = 2.75 %

I would like to add a constraint so that a lender can only appear once in the output, in the above example this would remove BA_1 from the output and add in WF_1.

The code that I have written for this constraint is below but is not applying the logic correctly:

unique_lenders = set([loan.lender for loan in loans])
print(unique_lenders)
for lender in unique_lenders:
   prob += lpSum([loanVars[loan] for loan in loans if loan.lender == lender]) >= 1

Thanks in advance.

EDIT:

I got the constraint working using a binary variable loanVars_selected and the below code based off Erwins answer:

for lender in unique_lenders:
prob += lpSum(loanVars_selected[loan] for loan in loans if loan.lender == lender) <= 1 

Solution

  • I think you want a binary variable for each loan. Then add:

      loanVars[loan] <= b[loan]*maxLoan[loan]
    

    and

      for each lender L:
             sum(for all loans issued by L,b[loan]) <= 1  
    

    (I used pseudo code for clarity)