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