I've got a fairly straightforward constrained non-linear optimisation problem, maximising revenue, given some spend constraints (keep overall spend constant, and increase/decrease spend on each channel by upto 50%) and an objective function which is revenue = spend * roi (where roi is calculated using the log-log model coefficients for each channel).
Firstly the solution doesn't match what I believe to be the optimal solution. Also, when take the optimal spend values suggested by GEKKO, and put them into the objective function, this also doesn't match what GEKKO gives as the optimal objective function value.
If the above problem isn't articulated very well, the example code below should bring to life the problem...
Anyone know what I'm missing?
Code below:
from gekko import GEKKO
import numpy as np
import pandas as pd
df1 = pd.DataFrame({'channel': ['fb', 'tv', 'ppc'],
'value': [5000, 5000, 5000],
'alpha': [1.00, 1.00, 1.00],
'beta': [0.03, 0.02, 0.01]
})
total_spend = df1['value'].sum()
m = GEKKO()
# Constraint parameters
spend_inc = 0.00
spend_dec = 0.00
channel_inc = 0.50
channel_dec = 0.50
channels = len(df1)
# Initialise decision variables
x = m.Array(m.Var, (channels), integer=True)
i = 0
for xi in x:
xi.value = df1['value'][i]
xi.lower = df1['value'][i] * (1 - channel_dec)
xi.upper = df1['value'][i] * (1 + channel_inc)
i += 1
# Initialise alpha
a = m.Array(m.Param, (channels))
i = 0
for ai in a:
ai.value = df1['alpha'][i]
i += 1
# Initialise beta
b = m.Array(m.Param, (channels))
i = 0
for bi in b:
bi.value = df1['beta'][i]
i += 1
# Initial global contraints
spend_min = total_spend * (1 - spend_dec)
spend_max = total_spend * (1 + spend_dec)
# Print out variabales
print(f'spend min: {spend_min}')
print(f'spend max: {spend_max}')
print('')
for i in range(0, channels):
print(f'x{i+1} value: {str(x[i].value)}')
print(f'x{i+1} lower: {str(x[i].lower)}')
print(f'x{i+1} upper: {str(x[i].upper)}')
print(f'x{i+1} alpha: {str(a[i].value)}')
print(f'x{i+1} beta: {str(b[i].value)}')
print('')
# Constraints
m.Equation(sum(x) >= spend_min)
m.Equation(sum(x) <= spend_max)
# Log-Log model
def roi(a, b, x):
roi = a + b * m.log(x[0])
roi = m.exp(roi[0])
return roi
# Objective function
m.Maximize(m.sum(x * roi(a, b, x)))
m.options.IMODE = 3
m.solve()
for i in range(0, channels):
print(f'x{i+1}: {str(x[i].value[0])}')
print('')
print(f'optimal solution: {str(m.options.objfcnval)}')
# THIS DOESN'T MATCH THE OBJECTIVE FUNCTION VALUE SUGGESTED BY GEKKO
opt = 0
for i in range(0, channels):
opt += x[i].value[0] * (np.exp((a[0].value[0] + b[i].value[0] * np.log(x[i].value[0]))))
print(f'optimal solution: {opt}')
# THIS IS THE EXPECETD SOLUTION, WHICH ALSO DOESN'T MATCH THE OBJECTIVE FUNCTION VALUE SUGGESTED BY GEKKO
7500 * np.exp(1.00 + 0.03 * np.log(7500)) + 5000 * np.exp(1.00 + 0.02 * np.log(5000)) + 2500 * np.exp(1.00 + 0.01 * np.log(2500))