I am trying to minimize a function with 2 variables x[0],x[1]. A, B, and C are dataframes with dimensions 10x10. The optimization works as intended when I don't use constraints, however I also care for the constrainted case. For the constrainted case, I want
A.iloc[i,j]*x[0]*B.iloc[i,j]*x[1]*C.iloc[i,j]
to be equal to or greater than zero for all combinations of i and j. To achieve this, I have defined constraints in the following way:
cons=[]
def f(a):
def g(x):
return A.iloc[i,j]*x[0]*B.iloc[i,j]*x[1]*C.iloc[i,j]
return g
for i in range (10):
for j in range(10):
cons.append({'type':'ineq', 'fun': f(t)})
While I am getting the right number of constraints (i.e. len(cons) = 100), the optimization results do not satisfy the constraints that I had in mind, meaning it results in values for x[0], x[1] and x[2] for which
A.iloc[i,j]*x[0]*B.iloc[i,j]*x[1]*C.iloc[i,j]
is smaller than zero for many j,i. I have ascertained that result.success = True, so the optimization suddenly stopping can be ruled out as a potential problem. While looking for a solution to this problem, I have found this case of someone trying to iterate constraints in scipy aswell, but they only iterated over one range rather than over two and I was not able to modify their solution to work for my case.
Your function f doesn't make any sense since the function g doesn't depend on a. If you really want f to return a new function depending on the indices i and t, f should be a function of the two indices:
cons=[]
def f(t, i):
def g(x):
return A.iloc[t,i]+(x[0]*B.iloc[t,i]+x[1]*C.iloc[t,i]+x[2]*D.iloc[t,i])/33
return g
for t in range (72):
for i in range(33):
cons.append({'type':'ineq', 'fun': f(t, i)})
Note also that you could easily define the constraint functions on the fly thanks to lambda expressions:
cons = []
for t in range(72):
for i in range(33):
cons.append({'type': 'ineq', 'fun': lambda x, t=t, i=i: A.iloc[t,i]+(x[0]*B.iloc[t,i]+x[1]*C.iloc[t,i]+x[2]*D.iloc[t,i])/33})
Related
I am trying to get a list of functions defined recursively by storing each step of a math serie:
x^0+x^1+x^2+x^3+... → f0(x)=1, f1(x)=1+x, f2(x)=1+x+x², f3(x)=1+x+x²+x³, ...
I coded this in python:
n = 3
devs = [lambda x: 1]
for k in range(n):
devs.append(lambda x, f=devs[k]: f(x) + x**(k+1))
print(devs[-1](4)) # x=4
I learned in this answer that you need to pass the previous function as default argument in order to solve the maximum recursion depth error, but this outputs 193.
I think it has to do with the k in the default parameter. It seems to calculate: 1+4^3+4^3+4^3=193, or more accurately 1+n*x^n instead of x^0+x^1+...+x^n
Can you please tell me what am I doing wrong here?
I think the same question you linked has the answer to your problem. Because the k inside the function is evaluated only outside the loop, it will have the last value from the loop. You can check the documention for how it works in depth.
The correct code is
n = 4
devs = [lambda x: 1]
for k in range(n):
devs.append(lambda x, f=devs[k], j=k: f(x) + x**(j+1))
One liner just for fun!
n=10
devs = (lambda f:[f] + [f := (lambda x, a=k, g=f: g(x) + x**(a+1)) for k in range(n)])(lambda x:1)
I can be considered pretty much new to python and coding in general so forgive me for my ignorance.
I'm trying to solve a system of trigonometric functions in python, and I'm doing so using the solve command from sympy. However, this method returns only a finite number of solutions, two in this particular case.
I've read through the documentation and it seems that to get an expression for all the solutions solveset is to be used instead. However, I do not want all the solutions to be displayed, but rather only a finite amount which is contained within a certain range.
Here's the example:
from sympy import *
x, y = symbols('x, y')
eq1 = Eq(y - sin(x), 0)
eq2 = Eq(y - cos(x), 0)
sol = solve([eq1, eq2], [x, y])
print(sol)
which only returns the first two solutions in the positive x range.
How could I do to, for example, display all the solutions within the x range [-2pi, 2pi]?
I'd want them in explicit form rather than written in term of some multiplier since I then need to convert them into numerical form.
Thank you in advance.
SymPy can really take you down rabbit holes. I agree with kampmani's solution, only if you can easily solve for y on your own. However, in more general cases and in higher dimensions, his solution does not hold.
For example, the following will be slightly more tricky:
eq1 = Eq(z - x*y, 0)
eq2 = Eq(z - cos(x) - sin(y), 0)
eq3 = Eq(z + x*y, 0)
So here I am; killing a fly with a bazooka. The problem is that one was able to simplify the set of equations into a single equation with a single variable. But what if you can't do that (for example, if it was a larger system)?
In this case, one needs to use nonlinsolve to solve the system of equations. But this does not provide numeric solutions directly and does not have a domain argument.
So the following code unpacks the solutions. It goes through each tuple in the set of solutions and finds the numeric solutions for each component in the tuple. Then in order to get the full list, you need a Cartesian Product of each of those components. Repeat this for each tuple in the set of solutions.
The following should work for almost any system of equations in any dimension greater than 1. It produces numeric solutions in the cube whose boundaries are the domains variable.
from sympy import *
import itertools # used for cartesian product
x, y, z = symbols('x y z', real=True)
domains = [Interval(-10, 10), Interval(-10, 10), Interval(-10, 10)] # The domain for each variable
eq1 = z - x*y
eq2 = z - cos(x) - sin(y)
eq3 = z + x*y
solutions = nonlinsolve([eq1, eq2, eq3], [x, y, z]) # the recommended function for this situation
print("---------Solution set----------")
print(solutions) # make sure the solution set is reasonable. If not, assertion error will occur
_n = Symbol("n", integer=True) # the solution set often seems to contain these symbols
numeric_solutions = []
assert isinstance(solutions, Set) # everything that I had tried resulted in a FiniteSet output
for solution in solutions.args: # loop through the different kinds of solutions
assert isinstance(solution, Tuple) # each solution should be a Tuple if in 2D or higher
list_of_numeric_values = [] # the list of lists of a single numerical value
for i, element in enumerate(solution):
if isinstance(element, Set):
numeric_values = list(element.intersect(domains[i]))
else: # assume it is an Expr
assert isinstance(element, Expr)
if _n.name in [s.name for s in element.free_symbols]: # if n is in the expression
# change our own _n to the solutions _n since they have different hidden
# properties and they cannot be substituted without having the same _n
_n = [s for s in element.free_symbols if s.name == _n.name][0]
numeric_values = [element.subs(_n, n)
for n in range(-10, 10) # just choose a bunch of sample values
if element.subs(_n, n) in domains[i]]
elif len(element.free_symbols) == 0: # we just have a single, numeric number
numeric_values = [element] if element in domains[i] else []
else: # otherwise we just have an Expr that depends on x or y
# we assume this numerical value is in the domain
numeric_values = [element]
# note that we may have duplicates, so we remove them with `set()`
list_of_numeric_values.append(set(numeric_values))
# find the resulting cartesian product of all our numeric_values
numeric_solutions += itertools.product(*list_of_numeric_values)
# remove duplicates again to be safe with `set()` but then retain ordering with `list()`
numeric_solutions = list(set(numeric_solutions))
print("--------`Expr` values----------")
for i in numeric_solutions:
print(list(i)) # turn it into a `list` since the output below is also a `list`.
print("--------`float` values---------")
for i in numeric_solutions:
print([N(j) for j in i]) # could have been converted into a `tuple` instead
In particular, it produces the following output for the new problem:
---------Solution set----------
FiniteSet((0, ImageSet(Lambda(_n, 2*_n*pi + 3*pi/2), Integers), 0))
--------`Expr` values----------
[0, -5*pi/2, 0]
[0, -pi/2, 0]
[0, 3*pi/2, 0]
--------`float` values---------
[0, -7.85398163397448, 0]
[0, -1.57079632679490, 0]
[0, 4.71238898038469, 0]
It was a lot of effort and probably not worth it but oh well.
By using solveset you can restrict the solutions with domain argument. For evaluating numerical results use .evalf() or another similar method.
from sympy import Interval, symbols, solveset, sin, cos, pi
x = symbols('x')
sol = solveset(cos(x) - sin(x), x, domain=Interval(-2*pi, 2*pi))
print(sol)
print(sol.evalf())
Output
FiniteSet(-7*pi/4, -3*pi/4, pi/4, 5*pi/4)
FiniteSet(-5.49778714378214, -2.35619449019234, 0.785398163397448, 3.92699081698724)
I hope this helps!
Thanks to the brilliant suggestion from #kampmani it is possible to achieve the desired result.
For start, the FiniteSet elements are not indexed and cannot be used, so the FiniteSet has to be converted into a list:
solx_array = []
#
#
#
solx = solveset(cos(x) - sin(x), x, domain=Interval(-2*pi, 2*pi))
solx_array=list(solx)
The next step is to find the y coordinate of the intersection point given its x coordinate. The final code should look somewhat similar to this:
from sympy import Interval, symbols, solveset, sin, cos, pi
sol_array = []
x = symbols('x')
solx = solveset(cos(x) - sin(x), x, domain=Interval(-2*pi, 2*pi))
solx_array=list(solx)
for i in range(len(solx_array)):
soly = cos(solx_array[i])
sol_array.append(str(solx_array[i] + soly))
for i in range(len(sol_array)):
print(sol_array[i])
Still don't know how to convert the results into numerical form though, any idea is very appreciated.
I want to write a custom function to integrate an expression (python or lambda function) numerically with a specific tolerance. I know with scipy.integrate.quad one can simply change the epsabs but I want to write the function myself using numpy.
From this blogpost I know the function:
def integrate(f, a, b, N):
x = np.linspace(a+(b-a)/(2*N), b-(b-a)/(2*N), N)
fx = f(x)
area = np.sum(fx)*(b-a)/N
return area
gives me the numerical integration with N segments. How can I write another function or extend this to take a tol input and increase N until the difference between the two subsequent calculations is smaller than the given tolerance?
Using the function you have, one can start with a reasonable N, for example 5, and keep doubling the number until the required precision is reached.
def integrate_tol(f, a, b, tol):
N = 5
old_integral = integrate(f, a, b, N)
while True:
N *= 2
new_integral = integrate(f, a, b, N)
if np.abs(old_integral - new_integral) < tol:
return (4*new_integral - old_integral)/3
old_integral = new_integral
A simple test:
f = lambda x: np.exp(x)
print(integrate_tol(f, -1, 1, 1e-9))
print(np.exp(1)-np.exp(-1)) # exact value for comparison
prints
2.3504023872876028
2.3504023872876028
There is no guarantee that the error is indeed less than tol (but then again, scipy.quad does not guarantee that either). In practice, the error will be much smaller than tol, because of the trick I used, called Richardson extrapolation: the return value (4*new_integral - old_integral)/3 is in general much more accurate than either new or old approximations themselves. (Explanation: since integrate uses the midpoint rule, each doubling of N reduces the error by the factor of about 4. So, taking the combination 4*new_integral - old_integral nearly cancels out the residual error in both of those results.)
(Remark: in the while loop it's unadvisable to start with N=1; it probably will not be enough, and there's higher risk of stopping too early because of some numerical coincidence, e.g., the function being zero in a bunch of places.)
I'm on Scipy Optimize, using the fmin_cobyla function. I've struggled to write constraint functions that:
make sure all items sum up to 1
make sure all items >= 0
Does anyone have insight? For fmin_slsqp, for example, I had a function:
def w_constraint(w, v, x0, x1):
return np.sum(w) - 1
As well as bounds in the function.
But these don't work on cobyla.
Formulate your equality as pair of inequalities. Meaning:
x == y becomes:
x >= y
x <= y = -x >= -y
Of course this may introduce numerical troubles (hurting many kinds of solvers), but i saw this in multiple implementations of COBYLA-interfaces (with equality-constraint support implemented like that).
A comment from the documentation of NLopt:
(The underlying COBYLA code only supports inequality constraints. Equality constraints are automatically transformed into pairs of inequality constraints, which in the case of this algorithm seems not to cause problems.)
For Scipy.optimize.minimize function, the following should work:
def constraint_func(x_in):
constraints_list = []
constraints_list.append({'type': 'ineq', 'fun': lambda x: np.sum(x)-1})
constraints_list.append({'type': 'ineq', 'fun': lambda x: -np.sum(x)+1})
for i in range(len(x_in)):
constraints_list.append({'type': 'ineq', 'fun': lambda x: x[i]})
return constraints_list
constraints = constraint_func(x0)
res = scipy.optimize.minimize(fun, x0, method='COBYLA', constraints= constraints)
I'm using scipy.optimize.minimize to optimize a real-world problem for which the answers can only be integers. My current code looks like this:
from scipy.optimize import minimize
def f(x):
return (481.79/(5+x[0]))+(412.04/(4+x[1]))+(365.54/(3+x[2]))+(375.88/(3+x[3]))+(379.75/(3+x[4]))+(632.92/(5+x[5]))+(127.89/(1+x[6]))+(835.71/(6+x[7]))+(200.21/(1+x[8]))
def con(x):
return sum(x)-7
cons = {'type':'eq', 'fun': con}
print scipy.optimize.minimize(f, [1,1,1,1,1,1,1,0,0], constraints=cons, bounds=([0,7],[0,7],[0,7],[0,7],[0,7],[0,7],[0,7],[0,7],[0,7]))
This yields:
x: array([ 2.91950510e-16, 2.44504019e-01, 9.97850733e-01,
1.05398840e+00, 1.07481251e+00, 2.60570253e-01,
1.36470363e+00, 4.48527831e-02, 1.95871767e+00]
But I want it optimized with integer values (rounding all x to the nearest whole number doesn't always give the minimum).
Is there a way to use scipy.optimize.minimize with only integer values?
(I guess I could create an array with all possible permutations of x and evaluate f(x) for each combination, but that doesn't seem like a very elegant or quick solution.)
pulp solution
After some research, I don't think your objective function is linear. I recreated the problem in the Python pulp library but pulp doesn't like that we're dividing by a float and 'LpAffineExpression'. This answer suggests that linear programming "doesn't understand divisions" but that comment is in context of adding constraints, not the objective function. That comment pointed me to "Mixed Integer Linear Fractional Programming (MILFP)" and on Wikipedia.
Here's how you could do it in pulp if it actually worked (maybe someone can figure out why):
import pulp
data = [(481.79, 5), (412.04, 4), (365.54, 3)] #, (375.88, 3), (379.75, 3), (632.92, 5), (127.89, 1), (835.71, 6), (200.21, 1)]
x = pulp.LpVariable.dicts('x', range(len(data)), lowBound=0, upBound=7, cat=pulp.LpInteger)
numerator = dict((i,tup[0]) for i,tup in enumerate(data))
denom_int = dict((i,tup[1]) for i,tup in enumerate(data))
problem = pulp.LpProblem('Mixed Integer Linear Programming', sense=pulp.LpMinimize)
# objective function (doesn't work)
# TypeError: unsupported operand type(s) for /: 'float' and 'LpAffineExpression'
problem += sum([numerator[i] / (denom_int[i] + x[i]) for i in range(len(data))])
problem.solve()
for v in problem.variables():
print(v.name, "=", v.varValue)
brute solution with scipy.optimize
You can use brute and ranges of slices for each x in your function. If you have 3 xs in your function, you'll also have 3 slices in your ranges tuple. The key to all of this is to add the step size of 1 to the slice(start, stop,step) so slice(#, #, 1).
from scipy.optimize import brute
import itertools
def f(x):
return (481.79/(5+x[0]))+(412.04/(4+x[1]))+(365.54/(3+x[2]))
ranges = (slice(0, 9, 1),) * 3
result = brute(f, ranges, disp=True, finish=None)
print(result)
itertools solution
Or you can use itertools to generate all combinations:
combinations = list(itertools.product(*[[0,1,2,3,4,5,6,7,8]]*3))
values = []
for combination in combinations:
values.append((combination, f(combination)))
best = [c for c,v in values if v == min([v for c,v in values])]
print(best)
Note: this is a scaled-down version of your original function for example purposes.
One thing that might help your problem you could have a constraint as:
max([x-int(x)])=0
This is not going to completely solve your problem, the algorithm will still try and cheat and you will get values with some level of error ~±5e-10 that it will still try and optimize towards just by the error in scipy's algorithm but it's better than nothing.
cons = ({'type':'eq', 'fun': con},
{'type':'eq','fun': lambda x : max([x[i]-int(x[i]) for i in range(len(x))])})
having tested this process on some optimizations I know the solution to, this process is more sensitive to the initial values than the unconstrained search, it gets fairly accurate answers however the solution may actually not find the true value, you are basically requiring the large jump of the optimization process (what it uses to make sure it's not optimizing to a local minimum) to search the sample space as the smaller increments are usually not strong enough to move to the next number over.
Here is a way to solve the Mixed Integer Nonlinear Programming problem with Python Gekko (a package that I maintain):
from gekko import GEKKO
m = GEKKO(remote=False)
x = m.Array(m.Var,9,lb=0,ub=7,integer=True)
def f(x):
return (481.79/(5+x[0]))+(412.04/(4+x[1]))\
+(365.54/(3+x[2]))+(375.88/(3+x[3]))\
+(379.75/(3+x[4]))+(632.92/(5+x[5]))\
+(127.89/(1+x[6]))+(835.71/(6+x[7]))\
+(200.21/(1+x[8]))
m.Minimize(f(x))
m.Equation(sum(x)==7)
m.options.SOLVER=1
m.solve()
print(x)
This gives the solution:
---------------------------------------------------
Solver : APOPT (v1.0)
Solution time : 0.0529 sec
Objective : 859.5269999999999
Successful solution
---------------------------------------------------
[[0.0] [0.0] [1.0] [1.0] [1.0] [0.0] [1.0] [0.0] [3.0]]
Generic function for bruteforce solution. Does somewhat a better job than brute in scipy, since scipy actually runs function with float numbers, not integers only, though range explicitly says so, as Jarad stated
def brute(func, arg_ranges, finish=min):
if isinstance(arg_ranges, dict):
args = {k:np.unique(np.hstack([a for r in rs for a in r]) if isinstance(rs, list) else [a for a in rs]) for k,rs in arg_ranges.items()}
print(args)
return finish([(dict(zip(args.keys(), vs)), func(**dict(zip(args.keys(), vs)))) for vs in itertools.product(*args.values())], key=lambda x: x[1])
elif isinstance(arg_ranges, list):
return finish([(i, func(i)) for r in arg_ranges for i in r], key=lambda x: x[1])
else:
return finish([(i, func(i)) for i in arg_ranges], key=lambda x: x[1])
print(brute(lambda x,y: x / (y + 2), {'x':[range(1,5,2), range(0,6,1)], 'y':range(2,5,1)}))