I'm using COBYLA to do a cost minimization on a linear objective function with constraints. I'm implementing lower and upper bounds by including a constraint for each.
import numpy as np
import scipy.optimize
def linear_cost(factor_prices):
def cost_fn(x):
return np.dot(factor_prices, x)
return cost_fn
def cobb_douglas(factor_elasticities):
def tech_fn(x):
return np.product(np.power(x, factor_elasticities), axis=1)
return tech_fn
def mincost(targets, cost_fn, tech_fn, bounds):
n = len(bounds)
m = len(targets)
x0 = np.ones(n) # Do not use np.zeros.
cons = []
for factor in range(n):
lower, upper = bounds[factor]
l = {'type': 'ineq',
'fun': lambda x: x[factor] - lower}
u = {'type': 'ineq',
'fun': lambda x: upper - x[factor]}
cons.append(l)
cons.append(u)
for output in range(m):
t = {'type': 'ineq',
'fun': lambda x: tech_fn(x)[output] - targets[output]}
cons.append(t)
res = scipy.optimize.minimize(cost_fn, x0,
constraints=cons,
method='COBYLA')
return res
COBYLA doesn't respect the upper or lower-bound constraints, but it does respect the technology constraint.
>>> p = np.array([5., 20.])
>>> cost_fn = linear_cost(p)
>>> fe = np.array([[0.5, 0.5]])
>>> tech_fn = cobb_douglas(fe)
>>> bounds = [[0.0, 15.0], [0.0, float('inf')]]
>>> mincost(np.array([12.0]), cost_fn, tech_fn, bounds)
x: array([ 24.00010147, 5.99997463])
message: 'Optimization terminated successfully.'
maxcv: 1.9607782064667845e-10
nfev: 75
status: 1
success: True
fun: 239.99999999822359
Why wouldn't COBYLA respect the first factor constraint (i.e. upper-bound # 15)?
COBYLA is in fact respecting all the bounds you give.
The problem lies in the construction of the cons list.
Namely, binding of variables in lambda and other inner-scoped functions in Python (and Javascript) is lexical, and does not behave in the way you assume: http://eev.ee/blog/2011/04/24/gotcha-python-scoping-closures/ After the loop is finished, the variables lower and upper have values 0 and inf, and variable factor has value 1, and these values are what is then used by all of the lambda functions.
One workaround is to explicitly bind the specific values of variables to dummy keyword arguments:
for factor in range(n):
lower, upper = bounds[factor]
l = {'type': 'ineq',
'fun': lambda x, a=lower, i=factor: x[i] - a}
u = {'type': 'ineq',
'fun': lambda x, b=upper, i=factor: b - x[i]}
cons.append(l)
cons.append(u)
for output in range(m):
t = {'type': 'ineq',
'fun': lambda x, i=output: tech_fn(x)[i] - targets[i]}
cons.append(t)
A second way is to add a factory function generating the lambdas.
Related
I would like to put an upper limit on the sum of abs(w) in a scipy optimization problem. This can be done in a linear program by using dummy variables, e.g. y > w, y > -w, sum(y) < K, but I cannot figure out how to formulate it in the scipy optimize framework.
Code example is below. This runs but the total portfolio gross is not fixed. This is a long/short portfolio optimization where the w's sum to zero, and I want abs(w) to sum to 1.0. Is there a way to add this second constraint in scipy's framework ?
import numpy as np
import scipy.optimize as sco
def optimize(alphas, cov, maxRisk):
def _calcRisk(w):
var = np.dot(np.dot(w.T, cov), w)
return(var)
def _calcAlpha(w):
alpha = np.dot(alphas, w)
return(-alpha)
constraints = (
{'type': 'eq', 'fun': lambda w: np.sum(w)},
{'type': 'ineq', 'fun': lambda w: maxRisk*maxRisk - _calcRisk(w)} )
n = len(alphas)
bounds = tuple((-1, 1) for x in range(n))
initw = n * [0.00001 / n]
result = sco.minimize(_calcAlpha, initw, method='SLSQP',
bounds=bounds, constraints=constraints)
return(result)
A simple algebraic trick will do. Since equality constraints tacitly mean that the constraint function result is to be zero, you just shift the function's output by 1.0. Since np.sum(w)-1.0=0.0 is equivalent to np.sum(w)=1.0. See the documentation on scipy.optimize.minimize. In turn, just change the line
{'type': 'eq', 'fun': lambda w: np.sum(w)},
to
{'type': 'eq', 'fun': lambda w: np.sum(w) - 1.0}
Thanks to folks who responded. The answer is to make the free variable vector bigger, and slice from it to get the variables as needed (obvious I guess :-). The following works (use at your own risk of course):
import numpy as np
import scipy.optimize as sco
# make the required lambda function "final" so it does not change when param i (or n) changes
def makeFinalLambda(i, n, op):
if op == '+':
return(lambda w: w[n+i] + w[i])
else:
return(lambda w: w[n+i] - w[i])
def optimize(alphas, cov, maxRisk):
n = len(alphas)
def _calcRisk(x):
w = x[:n]
var = np.dot(np.dot(w.T, cov), w)
return(var)
def _calcAlpha(x):
w = x[:n]
alpha = np.dot(alphas, w)
return(-alpha)
constraints = []
# make the constraints to create abs value variables
for i in range(n):
# note that this doesn't work; all the functions will refer to current i value
# constraints.append({'type': 'ineq', 'fun': lambda w: w[n+i] - w[i] })
# constraints.append({'type': 'ineq', 'fun': lambda w: w[n+i] + w[i] })
constraints.append({'type': 'ineq', 'fun': makeFinalLambda(i, n, '-') })
constraints.append({'type': 'ineq', 'fun': makeFinalLambda(i, n, '+') })
# add neutrality, gross value, and risk constraints
constraints = constraints + \
[{'type': 'eq', 'fun': lambda w: np.sum(w[:n]) },
{'type': 'eq', 'fun': lambda w: np.sum(w[n:]) - 1.0 },
{'type': 'ineq', 'fun': lambda w: maxRisk*maxRisk - _calcRisk(w)}]
bounds = tuple((-1, 1) for x in range(n))
bounds = bounds + tuple((0, 1) for x in range(n))
# try to choose a nice, feasible starting vector
initw = n * [0.001 / n]
initw = initw + [abs(w)+0.001 for w in initw]
result = sco.minimize(_calcAlpha, initw, method='SLSQP',
bounds=bounds, constraints=constraints)
return(result)
This iteratively creates 2 constraints for each weight variable to compute the absolute value variables. It's nicer to do this as a vector (per-element) constraint, as follows:
def optimize(alphas, cov, maxRisk):
n = len(alphas)
def _calcRisk(x):
w = x[:n]
var = np.dot(np.dot(w.T, cov), w)
return(var)
def _calcAlpha(x):
w = x[:n]
alpha = np.dot(alphas, w)
return(-alpha)
absfunpos = lambda x : [x[n+i] - x[i] for i in range(n)]
absfunneg = lambda x : [x[n+i] + x[i] for i in range(n)]
constraints = (
sco.NonlinearConstraint(absfunpos, [0.0]*n, [2.0]*n),
sco.NonlinearConstraint(absfunneg, [0.0]*n, [2.0]*n),
{'type': 'eq', 'fun': lambda w: np.sum(w[:n]) },
{'type': 'eq', 'fun': lambda w: np.sum(w[n:]) - 1.0 },
{'type': 'ineq', 'fun': lambda w: maxRisk*maxRisk - _calcRisk(w) } )
bounds = tuple((-1, 1) for x in range(n))
bounds = bounds + tuple((0, 3) for x in range(n))
initw = n * [0.01 / n]
initw = initw + [abs(w) for w in initw]
result = sco.minimize(_calcAlpha, initw, method='SLSQP',
bounds=bounds, constraints=constraints)
return(result)
I'm trying to get the global fit of multiple set of data considering a system of 4 coupled ODEs.
I have the working code that solves the system of 4 coupled ODEs for a single set of data, and I have the working code that do the global fit with an arbitrary function (but not using odeint).
My problem is that I'm not able to merge the two codes...
Code for coupled ODEs
t =
data=
def gauss(x, amp, sigma, center):
"""Gaussian lineshape."""
return amp * np.exp(-(x-center)**2 / (2.*sigma**2))
def f(xs, t, ps):
"""Lotka-Volterra predator-prey model."""
try:
amp = ps['amp'].value
center = ps['center'].value
sigma = ps['sigma'].value
T1 = ps['T1'].value
Teq = ps['Teq'].value
except Exception:
amp, center, sigma, T1, Teq = ps
s0,s1,s2,s3 = xs
return [- gauss(t,amp,sigma,center) * (s0-s1),\
gauss(t,amp,sigma,center) * (s0-s1) - s1/T1,\
(s1/T1 - s2/Teq),\
(s2/Teq)]
def g(t, x0, ps):
x = odeint(f, x0, t, args=(ps,))
return x
def residual(ps, ts, data):
x0 = ps['s0'].value, ps['s1'].value, ps['s2'].value, ps['s3'].value
b = ps['b'].value
model = (((g(ts, x0, ps)[:,0]-g(ts, x0, ps)[:,1]+g(ts, x0, ps)[:,2]+b*g(ts, x0, ps)[:,3]))**2)/((g(ts, x0, ps)[0,0]))**2
return (model - data).ravel()
# set parameters incluing bounds
params = Parameters()
params.add('s0', value=1, vary=False)
params.add('s1', value=0, vary=False)
params.add('s2', value=0, vary=False)
params.add('s3', value=0, vary=False)
params.add('amp', value=0.02)
params.add('center', value=5)
params.add('sigma', value=0.1)
params.add('T1', value=0.3)
params.add('Teq', value=0.7)
params.add('b', value=-1)
# fit model and find predicted values
result = minimize(residual, params, args=(t, data), method='leastsq')
final = data + result.residual.reshape(data.shape)
Considering the code here: https://lmfit.github.io/lmfit-py/examples/example_fit_multi_datasets.html
I've tried to do by myself the code for global fit in this case
def gauss(x, amp, sigma, center):
"""Gaussian lineshape."""
return amp * np.exp(-(x-center)**2 / (2.*sigma**2))
def f(xs, t, ps):
"""Lotka-Volterra predator-prey model."""
try:
amp = ps['amp'].value
center = ps['center'].value
sigma = ps['sigma'].value
T1 = ps['T1'].value
Teq = ps['Teq'].value
except Exception:
amp, center, sigma, T1, Teq = ps
s0,s1,s2,s3 = xs
return [- gauss(t,amp,sigma,center) * (s0-s1),\
gauss(t,amp,sigma,center) * (s0-s1) - s1/T1,\
(s1/T1 - s2/Teq),\
(s2/Teq)]
def g(t, x0, params):
"""
Solution to the ODE x'(t) = f(t,x,k) with initial condition x(0) = x0
"""
x = odeint(f, x0, t, args=(params,))
return x
def testmodel(params, ts, data):
x0 = params['s0'].value, params['s1'].value, params['s2'].value, params['s3'].value
b = params['b'].value
model = (((g(ts, x0, params)[:,0]-g(ts, x0, params)[:,1]+g(ts, x0, params)[:,2]+b*g(ts, x0, params)[:,3]))**2)/((g(ts, x0, params)[0,0]))**2
return model
def testmodel_dataset(params, i, x):
"""Calculate Gaussian lineshape from parameters for data set."""
x0 = params[f's0_{i+1}'], params[f's1_{i+1}'], params[f's2_{i+1}'], params[f's3_{i+1}']
amp = params[f'amp_{i+1}']
center = params[f'center_{i+1}']
sigma = params[f'sigma_{i+1}']
T1 = params[f'T1_{i+1}']
Teq = params[f'Teq_{i+1}']
b = params[f'b_{i+1}']
return testmodel(params, x, data)
def objective(params, x, data):
"""Calculate total residual for fits of Gaussians to several data sets."""
ndata, _ = data.shape
resid = 0.0*data[:]
# make residual per data set
for i in range(ndata):
resid[i, :] = data[i, :] - testmodel_dataset(params, i, x)
# now flatten this to a 1D array, as minimize() needs
return resid.flatten()
fit_params = Parameters()
for iy, y in enumerate(data):
fit_params.add(f's0_{iy+1}', value=1)
fit_params.add(f's1_{iy+1}', value=0)
fit_params.add(f's2_{iy+1}', value=0)
fit_params.add(f's3_{iy+1}', value=0)
fit_params.add(f'amp_{iy+1}', value=0.5)
fit_params.add(f'center_{iy+1}', value=0.5)
fit_params.add(f'sigma_{iy+1}', value=0.5)
fit_params.add(f'T1_{iy+1}', value=0.5)
fit_params.add(f'Teq_{iy+1}', value=0.4)
fit_params.add(f'b_{iy+1}', value=0.3)
for iy in (2, 3, 4, 5, 6):
fit_params[f'sigma_{iy}'].expr = 'sigma_1'
out = minimize(objective, fit_params, args=(x, data))
report_fit(out.params)
Result -> KeyError: 's0'
There is a problem with x0 and s0,s1,s2,s3, population of the four states.
I'm sorry if the question may be very naive...
Thank you for your help.
I have written a code in which functions are called in each other. The working code is as follows:
import numpy as np
from scipy.optimize import leastsq
import RF
func = RF.roots
# residuals = RF.residuals
def residuals(params, x, y):
return y - func(params, x)
def estimation(x, y):
p_guess = [1, 2, 0.5, 0]
params, cov, infodict, mesg, ier = leastsq(residuals, p_guess, args=(x, y), full_output=True)
return params
x = np.array([2.78e-03, 3.09e-03, 3.25e-03, 3.38e-03, 3.74e-03, 4.42e-03, 4.45e-03, 4.75e-03, 8.05e-03, 1.03e-02, 1.30e-02])
y = np.array([2.16e+02, 2.50e+02, 3.60e+02, 4.48e+02, 5.60e+02, 8.64e+02, 9.00e+02, 1.00e+03, 2.00e+03, 3.00e+03, 4.00e+03])
FIT_params = estimation(x, y)
print(FIT_params)
where RF file is:
def roots(params, x):
a, b, c, d = params
y = a * (b * x) ** c + d
return y
def residuals(params, x, y):
return y - func(params, x)
I would like to remove residuals function from the main code and use it by calling from RF file instead i.e. by activating the code line residuals = RF.residuals. By doing so, error NameError: name 'func' is not defined will be appeared. I put func argument in RF's residuals function as def residuals(func, params, x, y): which will face to error TypeError: residuals() missing 1 required positional argument: 'y'; It seems the error is related to the forth argument of the residuals function in this sample because it will get error for 'func' if the func argument be placed after the y argument. I couldn't find out the source of the issue, but I guess it must be related to limitation of arguments in functions. I would be appreciated if anyone could guide me to understand the error and its solution.
Is it possible to bring residual function from the main code to the RF file? How?
The problem is that there's no global variable func in your file RF.py, hence it can't be found. A simple solution would be to add an additional parameter to your residuals function:
# RF.py
def roots(params, x):
a, b, c, d = params
y = a * (b * x) ** c + d
return y
def residuals(params, func, x, y):
return y - func(params, x)
Then, you can use it inside your other file like this:
import numpy as np
from scipy.optimize import leastsq
from RF import residuals, roots as func
def estimation(func, x, y):
p_guess = [1, 2, 0.5, 0]
params, cov, infodict, mesg, ier = leastsq(residuals, p_guess, args=(func, x, y), full_output=True)
return params
x = np.array([2.78e-03, 3.09e-03, 3.25e-03, 3.38e-03, 3.74e-03, 4.42e-03, 4.45e-03, 4.75e-03, 8.05e-03, 1.03e-02, 1.30e-02])
y = np.array([2.16e+02, 2.50e+02, 3.60e+02, 4.48e+02, 5.60e+02, 8.64e+02, 9.00e+02, 1.00e+03, 2.00e+03, 3.00e+03, 4.00e+03])
FIT_params = estimation(func, x, y)
print(FIT_params)
I am trying to do something like this:
syms x h4 t4 c13;
t = 0.6*sin(pi*x);
h1x = 0.5*(1 - t);
h0 = h1x;
h14x = -h4 -t4*(x - 0.5);
h24x = h4 + t4*(x - 0.5);
symvar(h14x)
which returns
ans =
[ h4, t4, x]
Then
u13x = (-4*int(h14x, x, 0, x) + c13)/h0
symvar(u13x)
returns
u13x =
-(c13 + 4*x*(h4 - t4/2) + 2*t4*x^2)/((3*sin(pi*x))/10 - 1/2)
ans =
[ c13, h4, t4, x]
and
p12x = -3*int(u13x, x, 0, x)
symvar(p12x)
which is
p12x =
-3*int(-(c13 + 4*x*(h4 - t4/2) + 2*t4*x^2)/((3*sin(pi*x))/10 - 1/2), x, 0, x)
ans =
[ c13, h4, t4 ]
As you can see from u13x where the variables were [h4, t4, c13, x], while integrating to p12x it got reduced to [h4, t4, c13] even though the integral limits are variable (in terms of x). Is it a bug? I can't seem to get by this weird behaviour. Is there a workaround?
Here are three possible workarounds (tested in R2015a).
1. Use a symbolic function
One option is to make the input that will be passed to sym/symvar a symfun in terms of x and then use the optional second argument to specify a finite number of variable to look for:
syms x h4 t4 c13;
t = 0.6*sin(pi*x);
h1x = 0.5*(1 - t);
h0 = h1x;
h14x = -h4 -t4*(x - 0.5);
u13x = (-4*int(h14x, x, 0, x) + c13)/h0
p12x(x) = -3*int(u13x, x, 0, x) % Make symfun, function of x
n = realmax; % 4 or greater to get all variables in this case
symvar(p12x, n) % Second argument must be finite integer
which returns the expected [ x, t4, h4, c13]. Just setting the second argument to a very large integer value seems to work.
2. Convert expression to a string
There are actually two versions of symvar. There is symvar for string inputs and sym/symvar, in the Symbolic Math toolbox, for symbolic expressions. The two forms apparently behave differently in this case. So, another workaround is to convert the equation with int to a character string with sym/char before passing it to symvar and then converting the output back to a vector of symbolic variables:
syms x h4 t4 c13;
t = 0.6*sin(pi*x);
h1x = 0.5*(1 - t);
h0 = h1x;
h14x = -h4 -t4*(x - 0.5);
u13x = (-4*int(h14x, x, 0, x) + c13)/h0
p12x = -3*int(u13x, x, 0, x)
sym(symvar(char(p12x))).'
which also returns the expected [ c13, h4, t4, x] (note that order appears to be opposite of the first workaround above).
3. Call MuPAD function from Matlab
Lastly, you can call the MuPAD function indets that finds indeterminates in an expression.
syms x h4 t4 c13;
t = 0.6*sin(pi*x);
h1x = 0.5*(1 - t);
h0 = h1x;
h14x = -h4 -t4*(x - 0.5);
u13x = (-4*int(h14x, x, 0, x) + c13)/h0
p12x = -3*int(u13x, x, 0, x)
feval(symengine, 'x->indets(x) minus Type::ConstantIdents', p12x)
which returns [ x, c13, h4, t4]. This will work if the input p12x is class sym or symfun. You can also use:
evalin(symengine, ['indets(hold(' char(p12x) ')) minus Type::ConstantIdents'])
The reason that sym/symvar doesn't work in your case is because it is based on freeIndets under the hood, which explicitly ignores free variables in functions like int.
I'd like to integrate this equation numerically.
b = 62.5*10^-6;
a = 4*10^-6;
n_co = 1.473;
n_cl = n_co - 0.016;
n_s = 1.37868*10^-5;
L = 457.9*10^-9;
k = 2*%pi/L;
z = 0.4 ;
x =0.3;
int(exp(-1i*(2*k*(n_cl-n_s)*(sqrt(b^2 - x_p.^2))+2*k*(n_co-n_cl)*(sqrt(a^2-x_p.^2))))*exp(1i*((x-x_p).^2)/2*z),-a,a);
so what should i do to get result numerically. Since it contains exponential and complex functions hard to evaluate.
Thanks in advance
You can integrate a function f in the interval (from, to) with integral(f, from, to). For example, you can compute the value of the integral of exp(-x^2) from x=1 to 5 with:
f = #(x) exp(-x^2);
integral(f, 1, 5)