I am trying to solve KKT equations using sympy. All of the equations are symbolic and contain constants that are not given as numbers but as symbols. Alongside with the equations, there are also inequality constraints.
Is it possible to do this in sympy? If not, are there any alternatives?
An example would be:
Doing your question by hand, we get the first Lagrangian to be L = x**2 - bx + 1 - lambda(x - a).
Differentiating with respect to x and lambda and setting to 0, we solve to get x = a, lambda = 2a - b.
So if lambda < 0, we repeat with the new Lagrangian, L = x**2 - bx + 1.
This gives us 2 cases: If 2a - b < 0, then x = b/2, otherwise, x = a.
The following code reproduces this logic. Copy paste this into a file called lagrangian.py and see example 4 for your specific query.
"""
KKT Conditions:
Goal:
To minimise/maximise f(x_) subject to gi(x_) >= 0 for all i and hi(x_) == 0 for all i
where x_ refers to a vector x.
Variables with a `*` after them are optimal quantities.
1. gi(x*) and hi(x*) is feasible (that is, they are satisfied)
2. (df/dxj)(x*) - sum(lambdai* * (dgi/dxj)(x*)) - sum(mui* * (dhi/dxj)(x*)) = 0 for all j
3. lambdai* * gi(x*) = 0 for all i
4. lambdai >= 0 for all i
"""
from sympy import *
from typing import Iterable
def kkt(f, g=None, h=None, x=None):
"""
Finds the optimal values of `x` for `f` given the equalities `g[i] >= 0` for all i
and `h[i] == 0` for all i.
TODO: Remove the private variables _lambda and _mu from the output.
TODO: Make the output more user friendly.
Examples:
>>> from sympy import *
>>> from lagrangian import kkt
>>> x = list(symbols("x:3"))
Example 0 the most basic
>>> kkt(x[0]**2)
([x0], FiniteSet((0,)))
Example 1 from References
>>> kkt(2 * x[0] ** 2 + x[1] ** 2 + 4 * x[2]**2,
... h=[x[0] + 2*x[1] - x[2] - 6, 2 * x[0] - 2 * x[1] + 3 * x[2] - 12])
([_mu0, _mu1, x0, x1, x2], FiniteSet((504/67, 424/67, 338/67, 80/67, 96/67)))
Example 2 from References
>>> kkt(x[0] ** 2 + 2 * x[1] ** 2 + 3 * x[2] ** 2,
... [5 * x[0] - x[1] - 3 * x[2] - 3,
... 2 * x[0] + x[1] + 2 * x[2] - 6])
([_lambda0, x0, x1, x2], FiniteSet((72/35, 72/35, 18/35, 24/35)))
Example 3 from References
>>> kkt(4 * x[0] ** 2 + 2 * x[1] ** 2,
... [-2 * x[0] - 4 * x[1] + 15],
... [3 * x[0] + x[1] - 8])
([_mu0, x0, x1], FiniteSet((64/11, 24/11, 16/11)))
Example 4 for general KKT
>>> t, a, b = symbols("x a b")
>>> kkt(t ** 2 - b * t + 1, t - a, x=t)
Piecewise((([x], FiniteSet((b/2,))), 2*a - b < 0), (([_lambda0, x], FiniteSet((2*a - b, a))), True))
Warnings:
This function uses recursion and if queries are such as example 4 with many inequality
conditions, one will experience heavy performance issues.
References:
.. [1] http://apmonitor.com/me575/index.php/Main/KuhnTucker
Disadvantages:
- Does not allow for arbitrary number of inequalities and equalities.
- Does not work for functions of f that have an infinite
number of turning points and no equality constraints (I think)
"""
# begin sanity checks
if not g:
g = []
if not h:
h = []
if not x:
x = list(f.free_symbols)
if not isinstance(g, Iterable):
g = [g]
if not isinstance(h, Iterable):
h = [h]
if not isinstance(x, Iterable):
x = [x]
# end sanity checks
def grad(func):
"""Returns the grad of an expression or function `func`"""
grad_f = Matrix(len(x), 1, derive_by_array(func, x))
return grad_f
# define our dummy variables for the inequalities
if g:
_lambdas = list(symbols(f"_lambda:{len(g)}", real=True))
sum_g = sum([_lambdas[i] * g[i] for i in range(len(g))])
else:
_lambdas = []
sum_g = 0
# define our dummy variables for the equalities
if h:
_mus = list(symbols(f"_mu:{len(h)}", real=True))
sum_h = sum([_mus[i] * h[i] for i in range(len(h))])
else:
_mus = []
sum_h = 0
# define the lagrangian
lagrangian = f - sum_g - sum_h
# find grad of lagrangian
grad_l = grad(lagrangian)
# 1. feasibility conditions
feasible = Matrix(len(g) + len(h), 1, g + h)
# 2. combine everything into a vector equation
eq = grad_l.row_insert(0, feasible)
solution = solve(eq, x + _lambdas + _mus, set=True)
# 4. remove all non-binding inequality constraints
# for each _lambda solution, add a new solution with that inequality removed
# in the case that it is false.
pieces = []
for i, lamb in enumerate(_lambdas):
new_g = g[:i] + g[i + 1:]
lamb_sol_index = [i for i, s in enumerate(solution[0]) if s == lamb][0]
for solution_piece in solution[1]:
lamb_sol = solution_piece[lamb_sol_index]
try:
if lamb_sol >= 0: # we dont need to check the next kkt if the value is known.
continue
except TypeError: # error when inequality cannot be identified
pass
pieces.append((kkt(f, new_g, h, x), lamb_sol < 0))
pieces.append((solution, True))
return Piecewise(*pieces)
Related
I'm writing a not full-implemented Python function using SymPy library which looks for the critical points of a mathematical function f through the KKT conditions, as it follows:
def KKT(f: str, h=[], g=[], max=True):
# NOTE: The expressions contained in g must be such that g <= 0 and the ones contained in h must be such that h = 0. Both g and h are string lists
import sympy as sp # Importing a SymPy library
n = len(h) # Quantity of equality constraints
m = len(g) # Quantity of inequality constraints
f = sp.parse_expr(f) # Constructing the f function by passing a string as an argument
vars = f.free_symbols # Getting the variables set
pars = list(vars) # Parameters list
if n > 0:
for i in range(n):
exec(f'h{i+1} = sp.Symbol("h_{i+1}")')
exec(f'h{i+1} = sp.parse_expr(h[{i}])') # Create the equality constraint h_i
exec(f'l{i+1} = sp.Symbol("\\lambda_{i+1}")') # Create the parameter lambda_i
exec(f'pars.append(l{i+1})') # Adding lambda_i to parameters list
if m > 0:
for j in range(m):
exec(f'g_{j+1} = sp.Symbol("g_{j+1}")')
exec(f'g{j+1} = sp.parse_expr(g[{j}])') # Create the inequality constraint g_i
exec(f'u{j+1} = sp.Symbol("\\mu_{j+1}", negative=False)') # Create the parameter mu_i
exec(f'pars.append(u{j+1})') # Add mu_i to parameters list
exec(f'p{j+1} = sp.Symbol("p_{j+1}", negative=False)') # Create fill portion p_i
exec(f'pars.append(p{j+1})') # Add p_i to parameters list
# Creating the Lagrangean
L = f
if n > 0:
for i in range(n):
exec(f'L = L - l{i+1} * h{i+1}') 'Adding lambda_j * h_j to the Lagrangean
if m > 0:
for j in range(m):
exec(f'L = L - u{j+1} * g{j+1}') # <- THIS LINE IS NOT WORKING
# Adding mu_i * g_i to the Lagrangean
print(f'j: {j}')
print(f'{L}\n')
# Creating the KKT condition from Lagrangean
R = [] # Constraint's set
for var in vars:
R.append(sp.diff(L, var)) # Add the Lagrangean's partial derivative with respect to var
if n > 0:
for i in range(n):
exec(f'R.append(h{i+1})')
if m > 0:
for j in range(m):
exec(f'R.append(u{j+1} * g{j+1})')
exec(f'R.append(g{j+1} + p{j+1})')
# Solving KKT conditions
sols_lagr = sp.solve(R, pars, dict=True) # Lagrangian solutions
critical_points = [{var: sol.get(var) for var in sol if var in vars} for sol in sols_lagr]
return critical_points
KKT('24*x_1 - x_1**2 + 10*x_2 - 2*x_2**2',
[],
['x_1 - 8', 'x_2 - 7', '-x_1', '-x_2']
)
However, for some reason, the following line code doesn't work:
exec(f'L = L - u{j+1} * g{j+1}')
Because I'm getting this result when I execute this code's block:
j: 0
-x_1**2 + 24*x_1 - 2*x_2**2 + 10*x_2
j: 1
-x_1**2 + 24*x_1 - 2*x_2**2 + 10*x_2
j: 2
-x_1**2 + 24*x_1 - 2*x_2**2 + 10*x_2
j: 3
-x_1**2 + 24*x_1 - 2*x_2**2 + 10*x_2
Which shows the Lagrangean is not adding up the parcels. I'd be grateful if someone could help me.
I have an expression which contains terms like expr = a**m * b**n where a,b are symbols and m,n are integers. I want to use expr.subs(a**i * b**j, 0) to set the expression to zero if a**i * b**j is a factor of a**m * b**n (here i,j are integers too). This works in some cases, but not when only one of the symbols is used for the substitution:
>>> from sympy import symbols
>>> a, b = symbols('a b')
>>> (a**2 * b**3).subs(a**2 * b**2, 0) # (1) works
0
>>> (a**2 * b**3).subs(a * b**2, 0) # (2) works
0
>>> (a**2 * b**3).subs(b**2, 0) # (3) does not work
a**2*b**3
>>> (a**3).subs(a**2, 0) # (4) does not work
a**3
>>> (a**4).subs(a**2, 0) # (5) works
0
I would like this substitution to work in all of the above cases and logically it should (since for example a**2 * b**3 is equivalent to a**2 * b * (b**2) and thus the substitution (b**2, 0) should yield 0). However, if using a single symbol in the substitution, it seems to work only for those cases where the expression contains the subs-term raised to a power; for example:
>>> (a**2 * b**4).subs(b**2, 0) # (6) works
0
>>> (a**2 * b**4).subs(b**3, 0) # (7) does not work
a**2*b**4
This behavior seems a little odd since it works for b**1 and also when artificially augmenting both, the actual expression and the subs-term, with some other symbol:
>>> (a**2 * b**4).subs(b, 0) # (8) works
0
>>> c = symbols('c')
>>> (c * (a**2 * b**4)).subs(c * (b**3), 0) # (9) works
0
While I could use this last observation (augmenting expressions) as a workaround, it doesn't seem neither clear nor efficient.
Hence, is there another way to make the substitution work for all cases where the subs-term is contained in the actual expression?
>>> sympy.__version__
'1.10.1'
To check if something is a factor of a product you can use extract_multiplicatively:
>>> expr = a**3*b**3*c
>>> 0 if expr.extract_multiplicatively(a*b**2) else expr
0
You could also use the rem function to see if the remainder is 0:
>>> rem(expr, a*b**2)
0
If your expressions occur within a larger expression, then these could be incorporated into a replace call, expr.replace(...). If you are trying to ignore high-order terms in an expression you can just make a function to add the order of all symbol-based powers in a term:
>>> expr = expand((x + 1)**3*(y + 1)**2)
>>> def order(m):
... return sum([degree(m, x) for x in m.free_symbols])
...
>>> expr.replace(lambda x: not x.is_Add,
... lambda x: 0 if order(x)>2 else x)
3*x**2 + 6*x*y + 3*x + y**2 + 2*y + 1
works, because a2 = 0 is 0 * b3 what leads to 0
a = 0 means a**2 = 0 -> 0
b2 = 0 but b2 is not in b**3, so it does not work
same issue as 3
a4 is in a2 so 0
same as 5 but with b´s
same issue as 3
b is in b**4
c = c so it comes to 0 because only symbols get replaced
Everything works like intended
Say, we have f(t) = v * t + A * sin(w * t). I call such functions "saw-like":
I want to solve saw(t) = C, that is, find a root of saw(t) - C (still "saw-like").
I tried writing down a ternary search for function abs(saw(t) - C) to find its minima. If we are lucky (or crafty), it would be the root. Unfortunately, my code does not always work: sometimes we get stuck in those places:
My code (python3):
def calculate(fun):
eps = 0.000000001
eps_l = 0.1
x = terns(fun, 0, 100000000000000)
t = terns(fun, 0, x)
cnt = 0
while fun(x) > eps:
t = x
x = terns(fun, 0, t)
if abs(t - x) < eps_l:
cnt += 1
# A sorry attempt pass some wrong value as a right one.
# Gets us out of an infinite loop at least.
if cnt == 10:
break
return t
def terns(f, l, r):
eps = 0.00000000001
while r - l > eps:
x_1 = l + (r - l) / 3
x_2 = r - (r - l) / 3
if f(x_1) < f(x_2):
r = x_2
else:
l = x_1
return (l + r) / 2
So, how is it done? Is using ternary search the right way?
My other idea was somehow sending the equation over to the net, passing it to Wolfram Alpha and fetching the answers. Yet, I don't how it's done, as I am not quite fluent at python.
How could this be done?
I am not sure if it is an issue with my python code or with the latex but it keeps rearranging my equation in the output.
Code:
ddx = '\\frac{{d}}{{dx}}'
f = (a * x ** m) + (b * x ** n) + d
df = sym.diff(f)
df_string = tools.polytex(df)
f_string = tools.polytex(f)
question_stem = f"Find $_\\displaystyle {ddx}\\left({f_string}\\right)$_"
output:
In this case a = 9, b = -4, c = 4, m = (-1/2), n = 3 and I want the output to be in the order of the variable f.
I have tried changing the order to 'lex' and that did not work nor did .expand() or mode = equation
There is an order option for the StrPrinter. If you set the order to 'none' and then pass an unevaluated Add to _print_Add you can get the desired result.
>>> from sympy.abc import a,b,c,x,m,n
>>> from sympy import S
>>> oargs = Tuple(a * x ** m, b * x ** n, c) # in desired order
>>> r = {a: 9, b: -4, c: 4, m: -S.Half, n: 3}
>>> add = Add(*oargs.subs(r).args, evaluate=False) # arg order unchanged
>>> StrPrinter({'order':'none'})._print_Add(add)
9/sqrt(x) - 4*x**3 + 4
Probably this will not be possible in general, as SymPy expressions get reordered with every manipulation, and even with just converting the expression to the internal format.
Here is some code that might work for your specific situation:
from sympy import *
from functools import reduce
a, b, c, m, n, x = symbols("a b c m n x")
f = (a * x ** m) + (b * x ** n) + c
a = 9
b = -4
c = 4
m = -Integer(1)/2
n = 3
repls = ('a', latex(a)), ('+ b', latex(b) if b < 0 else "+"+latex(b)), \
('+ c', latex(c) if c < 0 else "+"+latex(c)), ('m', latex(m)), ('n', latex(n))
f_tex = reduce(lambda a, kv: a.replace(*kv), repls, latex(f))
# only now the values of the variables are filled into f, to be used in further manipulations
f = (a * x ** m) + (b * x ** n) + c
which leaves the following in f_tex:
9 x^{- \frac{1}{2}} -4 x^{3} 4
I want to solve an equation which I am supposed to solve it recursively, I uploaded the picture of formula (Sorry! I did not know how to write mathematical formulas here!)
I wrote the code in Python as below:
import math
alambda = 1.0
rho = 0.8
c = 1.0
b = rho * c / alambda
P0 = (1 - (alambda*b))
P1 = (1-(alambda*b))*(math.exp(alambda*b) - 1)
def a(n):
a_n = math.exp(-alambda*b) * ((alambda*b)**n) / math.factorial(n)
return a_n
def P(n):
P(n) = (P0+P1)*a(n) + sigma(n)
def sigma(n):
j = 2
result = 0
while j <= n+1:
result = result + P(j)*a(n+1-j)
j += 1
return result
It is obvious that I could not finish P function. So please help me with this.
when n=1 I should extract P2, when n=2 I should extract P3.
By the way, P0 and P1 are as written in line 6 and 7.
When I call P(5) I want to see P(0), P(1), P(2), P(3), P(4), P(5), P(6) at the output.
You need to reorganize the formula so that you don't have to calculate P(3) to calculate P(2). This is pretty easy to do, by bringing the last term of the summation, P(n+1)a(0), to the left side of the equation and dividing through by a(0). Then you have a formula for P(n+1) in terms of P(m) where m <= n, which is solvable by recursion.
As Bruce mentions, it's best to cache your intermediate results for P(n) by keeping them in a dict so that a) you don't have to recalculate P(2) etc everytime you need it, and b) after you get the value of P(n), you can just print the dict to see all the values of P(m) where m <= n.
import math
a_lambda = 1.0
rho = 0.8
c = 1.0
b = rho * c / a_lambda
p0 = (1 - (a_lambda*b))
p1 = (1-(a_lambda*b))*(math.exp(a_lambda*b) - 1)
p_dict = {0: p0, 1: p1}
def a(n):
return math.exp(-a_lambda*b) * ((a_lambda*b)**n) / math.factorial(n)
def get_nth_p(n, p_dict):
# return pre-calculated value if p(n) is already known
if n in p_dict:
return p_dict[n]
# Calculate p(n) using modified formula
p_n = ((get_nth_p(n-1, p_dict)
- (get_nth_p(0, p_dict) + get_nth_p(1, p_dict)) * a(n - 1)
- sum(get_nth_p(j, p_dict) * a(n + 1 - j) for j in xrange(2, n)))
/ a(0))
# Save computed value into the dict
p_dict[n] = p_n
return p_n
get_nth_p(6, p_dict)
print p_dict
Edit 2
Some cosmetic updates to the code - shortening the name and making p_dict a mutable default argument (something I try to use only sparingly) really makes the code much more readable:
import math
# Customary to distinguish variables that are unchanging by making them ALLCAP
A_LAMBDA = 1.0
RHO = 0.8
C = 1.0
B = RHO * C / A_LAMBDA
P0 = (1 - (A_LAMBDA*B))
P1 = (1-(A_LAMBDA*B))*(math.exp(A_LAMBDA*B) - 1)
p_value_cache = {0: P0, 1: P1}
def a(n):
return math.exp(-A_LAMBDA*B) * ((A_LAMBDA*B)**n) / math.factorial(n)
def p(n, p_dict=p_value_cache):
# return pre-calculated value if p(n) is already known
if n in p_dict:
return p_dict[n]
# Calculate p(n) using modified formula
p_n = ((p(n-1)
- (p(0) + p(1)) * a(n - 1)
- sum(p(j) * a(n + 1 - j) for j in xrange(2, n)))
/ a(0))
# Save computed value into the dict
p_dict[n] = p_n
return p_n
p(6)
print p_value_cache
You could fix if that way:
import math
alambda = 1.0
rho = 0.8
c = 1.0
b = rho * c / alambda
def a(n):
# you might want to cache a as well
a_n = math.exp(-alambda*b) * ((alambda*b)**n) / math.factorial(n)
return a_n
PCache={0:(1 - (alambda*b)),1:(1-(alambda*b))*(math.exp(alambda*b) - 1)}
def P(n):
if n in PCache:
return PCache[n]
ret= (P(0)+P(1))*a(n) + sigma(n)
PCache[n]=ret
return ret
def sigma(n):
# caching this seems smart as well
j = 2
result = 0
while j <= n+1:
result = result + P(j)*a(n+1-j)
j += 1
return result
void displayP(n):
P(n) # fill cache :-)
for x in range(n):
print ("%u -> %d\n" % (x,PCache[x]))
Instead of managing the cache manually, you might want to use a memoize decorator (see http://www.python-course.eu/python3_memoization.php )
Notes:
not tested, but you should get the idea behind it
your recurrence won't work P(n) depends on P(n+1) on your equation... This will never end
It looks like I misunderstood P0 and P1 as being Both constants (big numbers) and results (small numbers, indices)... Notation is not the best choice I guess...