Related
I am trying to find a common tangent to two curves using python but I am not able to solve it.
The equations to the two curves are complicated that involve logarithms.
Is there a way in python to compute the x coordinates of a tangent that is common to both the curves in general. If I have 2 curves f(x) and g(x), I want to find the x-coordinates x1 and x2 on a common tangent where x1 lies on f(x) and x2 on g(x). I am trying f'(x1) = g'(x2) and f'(x1) = f(x1) - f(x2) / (x1 - x2) to get x1 and x2 but I am not able to get values using nonlinsolve as the equations are too complicated.
I want to just find x-coordinates of the common tangent
Can anyone suggest a better way?
import numpy as np
import sympy
from sympy import *
from matplotlib import pyplot as plt
x = symbols('x')
a, b, c, d, e, f = -99322.50019502985, -86864.87072433547, -96876.05627516498, -89703.35055202093, -3390.863799999999, -20942.518
def func(x):
y1_1 = a - a*x + b*x
y1_2 = c - c*x + d*x
c1 = (1 - x) ** (1 - x)
c2 = (x ** x)
y2 = 12471 * (sympy.log((c1*c2)))
y3 = 2*f*x**3 - x**2*(e + 3*f) + x*(e + f)
eqn1 = y1_1 + y2 + y3
eqn2 = y1_2 + y2 + y3
return eqn1, eqn2
val = np.linspace(0, 1)
f1 = sympy.lambdify(x, func(x)[0])(val)
f2 = sympy.lambdify(x, func(x)[1])(val)
plt.plot(val, f1)
plt.plot(val, f2)
plt.show()
I am trying this
x1, x2 = sympy.symbols('x1 x2')
fun1 = func(x1)[0]
fun2 = func(x2)[0]
diff1 = diff(fun1,x1)
diff2 = diff(fun2,x2)
eq1 = diff1 - diff2
eq2 = diff1 - ((fun1 - fun2) / (x1 - x2))
sol = nonlinsolve([eq1, eq2], [x1, x2])
the first thing that needs to be done is to reduce the formulas
for example the first formula is actually this:
formula = x*(1 - x)*(17551.6542 - 41885.036*x) + x*(1 - x)*(41885.036*x - 24333.3818) + 12457.6294706944*x + log((x/(1 - x))**(12000*x)*(1 - x)**12000) - 99322.5001950298
formula = (x-x^2)*(17551.6542 - 41885.036*x) + (x-x^2)*(41885.036*x - 24333.3818) + 12457.6294706944*x + log((x/(1 - x))**(12000*x)*(1 - x)**12000) - 99322.5001950298
# constants
a = 41885.036
b = 17551.6542
c = 24333.3818
d = 12457.6294706944
e = 99322.5001950298
f = 12000
formula = (x-x^2)*(b - a*x) + (x-x^2)*(a*x - c) + d*x + log((x/(1 - x))**(f*x)*(1 - x)**f) - e
formula = (ax^3 -bx^2 + bx - ax^2) + (x-x^2)*(a*x - c) + d*x + log((x/(1 - x))**(f*x)*(1 - x)**f) - e
formula = ax^3 -bx^2 + bx - ax^2 -ax^3 + ax^2 + cx^2 -cx + d*x + log((x/(1 - x))**(f*x)*(1 - x)**f) - e
# collect x terms by power (note how the x^3 tern drops out, so its easier).
formula = (c-b)*x^2 + (b-c+d)*x + log((x/(1 - x))**(f*x)*(1 - x)**f) - e
which is much cleaner and is a quadratic with a log term.
i expect that you can do some work on the log term too, but this is an excercise for the original poster.
likewise the second formula can be reduced in the same way, which is again an excercise for the original poster.
From this, both equations need to be differentiated with respect to x to find the tangent. Then set both formulas to be equal to each other (for a common tangent).
This would completely solve the question.
I actually wonder if this is a python question at all or actually a pure maths question.....
The important point to note is that, since the derivatives are monotonic, for any value of derivative of fun1, there is a solution for fun2. This can be easily seen if you plot both derivatives.
Thus, we want a function that, given an x1, returns an x2 that matches it. I'll use numerical solution because the system is too cumbersome for numerical solution.
import scipy.optimize
def find_equal_value(f1, f2, x, x1):
goal = f1.subs(x, x1)
to_solve = sympy.lambdify(x, (f2 - goal)**2) # Quadratic functions tend to be better behaved, and the result is the same
sol = scipy.optimize.fmin(func=to_solve, x0=x1, ftol=1e-8, disp=False) # The value for f1 is a good starting guess
return sol[0]
I used fmin as the solver above because it worked and I knew how to use it by heart. Maybe root_scalar can give better results.
Using the function above, let's get some pairs (x1, x2) where the derivatives are equal:
df1 = sympy.diff(func(x)[0])
df2 = sympy.diff(func(x)[1])
x1 = 0.25236537 # Close to the zero derivative
x2 = find_equal_value(df1, df2, x, x1)
print(f'Derivative of f1 in x1: {df1.subs(x, x1)}')
print(f'Derivative of f2 in x2: {df2.subs(x, x2)}')
print(f'Error: {df1.subs(x, x1) - df2.subs(x, x2)}')
This results is:
Derivative of f1 in x1: 0.0000768765858083498
Derivative of f2 in x2: 0.0000681969431752805
Error: 0.00000867964263306931
If you want a x2 for several x1s (beware that in some cases the solver hits a value where the logs are invalid. Always check your result for validity):
x1s = np.linspace(0.2, 0.8, 50)
x2s = [find_equal_value(df1, df2, x, x1) for x1 in x1s]
plt.plot(x1s, x2s); plt.grid(); plt.show()
I've been digging in stackoverflow for a while and can't find any example for multiple piecewise curve fitting. I want to convert a quadratic function into multiple chaining (I don't know the exact name of it, but i need every tail connected to the head of the next piecewise, simply "connected") of piecewise function. This is my code so far using scipy.optimize to convert quadratic into 2 pieces of piecewise linear function.
import scipy.optimize as opt
import numpy as np
import copy
def func_2piecewise(x, m_0, x_1, y_1, m_1):
y = np.piecewise(x, [x <= x_1, x > x_1],
[lambda x:m_0*(x-x_1) + y_1, lambda x:m_1*(x-x_1) + y_1])
return y
xmin=0
xmax=100
a=0.1
a0=1
a00=10
piece_number=2
sigma=np.ones(numberOfStep)
if piece_number==2:
lower_bounds=[-np.inf,xmin,-np.inf,-np.inf]
upper_bounds=[np.inf,xmax,np.inf,np.inf]
w, _ = opt.curve_fit(func_2piecewise, x_sample, y_sample,bounds=(lower_bounds,upper_bounds),sigma=sigma)
x_0=copy.deepcopy(xmin)
y_0=func_2piecewise(x_0, *w).tolist()
[m_0, x_1, y_1, m_1]=w
result=[x_0,y_0,m_0,x_1,y_1,m_1]
The problem is, I can't implement the same approach for three piecewise (i don't know how to make x_2 > x_1):
def func_gradients(x_list,y_list):
len_x_list=len(x_list)
if len_x_list==1:
m_list=y_list/x_list
return m_list
m_list=[]
for idx in range(len_x_list-1):
m_list.append((y_list[idx+1]-y_list[idx])/(x_list[idx+1]-x_list[idx]))
return m_list
def func_3piecewise(x, m_0, x_1, y_1, x_2, y_2, m_2):
y = np.piecewise(x, [x <= x_1, (x > x_1) & (x <= x_2), x > x_2],
[lambda x:m_0*(x-x_1) + y_1, lambda x:y_1+(y_2-y_1)*(x-x_1)/(x_2-x_1), lambda x:m_2*(x-x_2) + y_2])
return y
if piece_number==3:
lower_bounds=[-np.inf,xmin,-np.inf,xmin,-np.inf,-np.inf]
upper_bounds=[np.inf,xmax,np.inf,xmax,np.inf,np.inf]
w, _ = opt.curve_fit(func_3piecewise, x_sample, y_sample,bounds=(lower_bounds,upper_bounds),sigma=sigma)
x_0=copy.deepcopy(xmin)
y_0=func_3piecewise(x_0, *w).tolist()
[m_0, x_1, y_1, x_2, y_2, m_2]=w
m_1=func_gradients(x_2-x_1,y_2-y_1)
result=[x_0,y_0,m_0,x_1,y_1,m_1, x_2, y_2, m_2]
The full code can be seen in pastebin
So, the question is:
How to make a chaining (every tail of the piecewise function connected to the head of the next piece, or simply "connected") picewise function in python for general n-pieces? Other algorithm or solver is acceptable.
Edit: I add my result so far for 2 piecewise.
Update: I found that my code (for three pieces) is not working because of a small typo (sorry about this, just tell me if I should delete this question). Now it's working and I update the paste bin. But, if you have a general (flexible, no need to write function for each number variant) function that can generate n number of pieces,I'll gladly accept the answer.
You can parametrize on the distance x2-x1 instead of parametrizing on x2. Because you can give the optimizer bounds, you can set the distance to be greater than 0.
For example, to make a general piecewise-linear function with 4 intervals, define the following:
The points which separate the intervals and x0, x1 and x2. The slopes in the 4 intervals are m0, m1, m2 and m3. The value of the function at x0 is y0.
Define d1 = x1 - x0, d2 = x2 - x1. From here:
x1 = x0 + d1
x2 = x0 + d1 + d2
Then, you have 8 optimization parameters: x0, y0, d1, d2, m0, m1, m2 and m3. By nature of your optimization problem, all except x0 and y0 are non-negative.
Equation for the first interval:
y = m0 * (x - x0) + y0
Equation for the second interval:
y = m1 * (x - x0) + y0
Now you can get the rest of the equations in a recursive way, by applying the previous equation at the rightmost point of its interval. For the x1 point, the value of the function is:
y1 = m1 * d1 + y0
So the third equation is
y =
m2 * (x - x1) + y1 =
m2 * (x - x0 - d1) + m1 * d1 + y0
For the x2 point, this gives
y2 = m2 * d2 + y1
So the fourth equation is
y =
m3 * (x - x2) + y2 =
m3 * (x - x0 - d1 - d2) + m2 * d2 + m1 * d1 + y0
I am trying to solve my equasions this way:
a = np.array([[1,2,4,1,0,2],[0,1,2,0,0,1],[0,0,0,2,2,0],[0,0,0,0,14,4],[0,0,0,0,0,-2]])
b = np.array([3,0,1,0,14])
x = np.linalg.solve(a,b)
However, as they are not full ranked there is no one solution, but instead endless solutions. Usually I would simply insert a parameter, like x3 = t when solving this. Then I have a solution where x2 and x1 can also contain t. But how can I tell python to solve it this way? Or at least tell it that x3 is t and to continue using that?
I know there is the leastsquares way, but that's not what I'm looking for.
Edit: The solution will look something like that: x6 == -7 && x5 == 2 && x4 == -(3/2) && x2 == 7 - 2 x3 && x1 == 9/2 - made with Mathematica. Just want to know how to achieve the same result in python.
Using SymPy,
import numpy as np
import sympy as sym
a = np.array([[1,2,4,1,0,2],[0,1,2,0,0,1],[0,0,0,2,2,0],[0,0,0,0,14,4],[0,0,0,0,0,-2]])
b = np.array([3,0,1,0,14])
num_equations, num_variables = a.shape
x = sym.symarray('x', num_variables)
solution = sym.solve([sym.Eq(ax-b) for ax, b in zip(np.dot(a, x), b)])
print(solution)
yields
{x_5: -7, x_4: 2, x_3: -3/2, x_1: -2*x_2 + 7, x_0: 9/2}
There's a math side of this and a programming side of this. On the math side, it's important to note that if ax=b has multiple solutions, then those solutions are {y + b1 * t1 + b_2 * t_2 + ... + bN * tN | t1, ..., tN in the real numbers} where y is any solution to ax=b (such as the least-squares solution) and b1, ..., bN are basis vectors for the null space of a. On the programming side, np.linalg.lstsq gets a least squares solution, and scipy.linalg.null_space gets the null space. One way you could put these together to get output similar to what you want is as follows.
import numpy as np
import scipy.linalg
import sys
def print_parameterized_form(a, b):
one_solution = np.linalg.lstsq(a, b, rcond=None)[0]
null_space_basis = scipy.linalg.null_space(a)
for i in range(a.shape[1]):
sys.stdout.write('x{} = {}'.format(i, one_solution[i]))
for j in range(null_space_basis.shape[1]):
sys.stdout.write(' + ({}) * t{}'.format(null_space_basis[i, j], j))
sys.stdout.write('\n')
a = np.array([[1,2,4,1,0,2],[0,1,2,0,0,1],[0,0,0,2,2,0],[0,0,0,0,14,4],[0,0,0,0,0,-2]])
b = np.array([3,0,1,0,14])
print_parameterized_form(a, b)
This should give you something like this:
x0 = 4.500000000000011 + (-3.5160449919006082e-15) * t0
x1 = 1.4000000000000128 + (0.8944271909999162) * t0
x2 = 2.7999999999999887 + (-0.4472135954999573) * t0
x3 = -1.499999999999997 + (9.065580383436411e-17) * t0
x4 = 2.0000000000000004 + (4.62652890306841e-18) * t0
x5 = -6.999999999999999 + (1.86607760441072e-16) * t0
I would like to solve in Python the following Mixed-Integer Quadratic Programming in Python. Nevertheless, I'm not familiar with the optimization
toolboxes of Python.
Can someone provide an example of code with the vectors X1, X2, X3, X4 given as below ?
X1 = np.array([3,10,20,10])
X2 = np.array([5,1,3,4])
X3 = np.array([2,3,1,4])
X4 = np.array([10,0,1,2])
The MIQP is written as :
I tried to solve it with CVXPY but i encoutered problem with the boolean
variable x = cp.Variable(1, boolean=True):
import numpy
import numpy as np
import cvxpy as cp
X1 = np.array([3,10,20,10])
X2 = np.array([5,1,3,4])
X3 = np.array([2,3,1,4])
X4 = np.array([10,0,1,2])
M = 100
x = cp.Variable(1, boolean=True)
Y1 = cp.Parameter(4)
Y2 = cp.Parameter(4)
a = cp.Parameter(1)
b = cp.Parameter(1)
c = cp.Parameter(1)
d = cp.Parameter(1)
delta = cp.Variable(1)
constraints = [Y1 <= X1 - a,
Y1 <= X2 - b,
Y1 >= X1 - a - M*delta,
Y1 >= X2 - b - M*(1-delta),
Y2 <= X3 - c,
Y2 <= X4 - d,
Y2 >= X3 - c - M*delta,
Y2 >= X4 - d - M*(1-delta),
0 <= a, a <= 10,
0 <= b, b <= 5,
0 <= c, c <= 5,
0 <= d, d <= 10,
delta == x]
obj = cp.Minimize(cp.sum_squares(Y1-Y2))
prob = cp.Problem(obj, constraints)
print(prob.solve())
In cvxpy, parameter is something you have a value to set to it. In your problem, basically all symbols other than the X1 to X4 are variables. So do a global replace of cp.Parameter to cp.Variable will work.
Then, I found the result to be
$ python3 cvxtest.py
69.99998471073722
Gekko with the APOPT solver can handle MIQP problems in addition to more general Nonlinear Mixed Integer Programming (MINLP). The solution is:
---------------------------------------------------
Solver : APOPT (v1.0)
Solution time : 2.610000000277068E-002 sec
Objective : 70.0000000000000
Successful solution
---------------------------------------------------
x: 1.0
obj: 70.0
Here is the Python script:
import numpy as np
from gekko import GEKKO
m = GEKKO()
X1 = m.Param([3,10,20,10])
X2 = m.Param([5,1,3,4])
X3 = m.Param([2,3,1,4])
X4 = m.Param([10,0,1,2])
M = 100
p = m.Array(m.FV,4,lb=0,ub=10); a,b,c,d=p
b.upper = 5; c.upper = 5
for pi in p:
pi.STATUS=1
x = m.FV(lb=0,ub=1,integer=True); x.STATUS=1
Y1,Y2 = m.Array(m.Var,2)
delta = m.FV(); delta.STATUS=1
m.Equations([Y1 <= X1 - a,
Y1 <= X2 - b,
Y1 >= X1 - a - M*delta,
Y1 >= X2 - b - M*(1-delta),
Y2 <= X3 - c,
Y2 <= X4 - d,
Y2 >= X3 - c - M*delta,
Y2 >= X4 - d - M*(1-delta),
delta == x])
m.Minimize((Y1-Y2)**2)
m.options.IMODE=2
m.options.SOLVER=1
m.solve()
print('x: ', x.value[0])
print('obj: ', m.options.OBJFCNVAL)
I am trying to solve the following system of linear equations:
#x0 + 1/10 * (x1 + x2 + x3 + x4) = 10
#x1 + 1/15 * (x1 + x2 + x3 + x4 + x5) = 20
#x2 + 1/21 * (x1 + x2 + x3 + x4 + x5 + x6) = 30
To do so I am doing:
a = np.array([[1,1/10,1/10,1/10,1/10,0,0],[0,1+1/15,1/15,1/15,1/15,1/15,0],[0,1/21,1+1/21,1/21,1/21,1/21]])
b=np.array([10,20,30])
x = np.linalg.solve(a, b)
For which I get the following error:
LinAlgError: 1-dimensional array given. Array must be at least two-dimensional
Can someone point out what am I doing wrong? The array has correct entries as I checked. I am following the example here
It looks to me as though there is an error in the last line of the matrix - I count 5 commas rather than 6.
With your version a.shape is (3,)
a = np.array([[1,1/10,1/10,1/10,1/10,0,0],[0,1+1/15,1/15,1/15,1/15,1/15,0],[0,1/21,1+1/21,1/21,1/21,1/21,1/21]])
With the missing element as above, a.shape is (3,7) which seems correct.
Also as noted in the comment, you have 7 unknowns and only 3 equations so you have an underdetermined system.