Skip to content

Instantly share code, notes, and snippets.

@cheind
Last active April 12, 2021 10:01
Show Gist options
  • Save cheind/27062b87009e1dc305590ba729b87f3a to your computer and use it in GitHub Desktop.
Save cheind/27062b87009e1dc305590ba729b87f3a to your computer and use it in GitHub Desktop.
Linear Programming for Optimizing Funding Costs. See https://doi.org/10.5281/zenodo.4607219 for documentation.
from scipy.optimize import linprog
import numpy as np
import pandas as pd
def print_metrics(df):
print('Total staff costs', df.to_numpy().sum())
print('Management cost ratio')
print(df.MgtStaffCosts / df.to_numpy().sum())
print('Partner cost ratio')
print(df.sum(1) / df.to_numpy().sum())
def setup_constraint_a(df, r):
N = len(df)
A = np.full((N, 2*N), -r)
np.fill_diagonal(A, 1.0 - r)
b = np.zeros(N)
return A, b
def setup_constraint_b(df, st_range):
N = len(df)
# Upper bound
A_ub = np.full((N, 2*N), -st_range[1])
np.fill_diagonal(A_ub, 1.0 - st_range[1])
np.fill_diagonal(A_ub[:, N:], 1.0 - st_range[1])
# Lower bound
A_lb = np.full((N, 2*N), -st_range[0])
np.fill_diagonal(A_lb, 1.0 - st_range[0])
np.fill_diagonal(A_lb[:, N:], 1.0 - st_range[0])
A_lb *= -1
b = np.zeros(2*N)
return np.block([[A_ub], [A_lb]]), b
def main():
np.set_printoptions(precision=2, floatmode='maxprec', suppress=True)
df = pd.DataFrame(
[
('A', 107214, 25500),
('B', 94674, 17000),
('C', 40000, 24000),
],
columns=['Company', 'OtherStaffCosts', 'MgtStaffCosts']
).set_index('Company')
"""Maximum management staff cost ratio."""
R = 0.07
"""Allowed staff costs ratio per partner."""
ST = (0.1, 0.5)
N = len(df)
print(df)
print_metrics(df)
# Note, linprog minimizes the objective, so we invert it
c = -np.ones(2*N)
# Setup constraints a) and b)
A0, b0 = setup_constraint_a(df, R)
A1, b1 = setup_constraint_b(df, ST)
# Fusion in a large constraint matrix
A = np.block([[A0], [A1]])
b = np.concatenate((b0, b1))
# Constraint c) can be given as bounds.
bounds = [
(0, xi) for xi in df.MgtStaffCosts.to_numpy()
]
bounds.extend([
(0, yi) for yi in df.OtherStaffCosts.to_numpy()
])
res = linprog(c, A_ub=A, b_ub=b, bounds=bounds, method='revised simplex')
print(pd.Series(np.asarray(res.x[:N])))
print()
print('optimized:')
dfopt = df.copy()
dfopt.loc[:, 'MgtStaffCosts'] = res.x[:N]
dfopt.loc[:, 'OtherStaffCosts'] = res.x[N:]
print(dfopt)
print_metrics(dfopt)
if __name__ == '__main__':
main()
@cheind
Copy link
Author

cheind commented Apr 12, 2021

Update for a new constraint that popped up during discussion.

def setup_constraint_c(df, r):
    '''Total management costs / sum of all costs <= r
        sum(xi)/(sum(xi) + sum(yi)) <= r    
    '''
    N = len(df)
    A = np.zeros((1, 2 * N))
    A[0, :N] = 1.0 - r
    A[0, N:] = -r
    b = np.zeros(1)

    return A, b

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment