Find common tangent line between two cubic curves
Question:
Given two functions, I would like to sort out the common tangent for both curves:
The slope of the common tangent can be obtained by the following:
slope of common tangent = (f(x1) - g(x2)) / (x1 - x2) = f'(x1) = g'(x2)
So that in the end we have a system of 2 equations with 2 unknowns:
f'(x1) = g'(x2) # Eq. 1
(f(x1) - g(x2)) / (x1 - x2) = f'(x1) # Eq. 2
For some reason I do not understand, python does not find the solution:
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import sys
from sympy import *
import sympy as sym
# Intial candidates for fit
E0_init = -941.510817926696
V0_init = 63.54960592453
B0_init = 76.3746233515232
B0_prime_init = 4.05340727164527
# Data 1 (Red triangles):
V_C_I, E_C_I = np.loadtxt('./1.dat', skiprows = 1).T
# Data 14 (Empty grey triangles):
V_14, E_14 = np.loadtxt('./2.dat', skiprows = 1).T
def BM(x, a, b, c, d):
return (2.293710449E+17)*(1E-21)* (a + b*x + c*x**2 + d*x**3 )
def P(x, b, c, d):
return -b - 2*c*x - 3 *d*x**2
init_vals = [E0_init, V0_init, B0_init, B0_prime_init]
popt_C_I, pcov_C_I = curve_fit(BM, V_C_I, E_C_I, p0=init_vals)
popt_14, pcov_14 = curve_fit(BM, V_14, E_14, p0=init_vals)
x1 = var('x1')
x2 = var('x2')
E1 = P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - P(x2, popt_14[1], popt_14[2], popt_14[3])
print 'E1 = ', E1
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
sols = solve([E1, E2], [x1, x2])
print 'sols = ', sols
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0], V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1], 10000)
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black', label='Cubic fit data 1' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b', label='Cubic fit data 2')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='Data 1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='Data 2', s=100)
plt.ticklabel_format(useOffset=False)
plt.show()
1.dat
is the following:
61.6634100000000 -941.2375622594436
62.3429030000000 -941.2377748739724
62.9226515000000 -941.2378903605746
63.0043440000000 -941.2378981684135
63.7160150000000 -941.2378864590100
64.4085050000000 -941.2377753645115
65.1046835000000 -941.2375332100225
65.8049585000000 -941.2372030376584
66.5093925000000 -941.2367456992965
67.2180970000000 -941.2361992239395
67.9311515000000 -941.2355493856510
2.dat
is the following:
54.6569312500000 -941.2300821583739
55.3555152500000 -941.2312112888004
56.1392347500000 -941.2326135552780
56.9291575000000 -941.2338291772218
57.6992532500000 -941.2348135408652
58.4711572500000 -941.2356230099117
59.2666985000000 -941.2362715934311
60.0547935000000 -941.2367074271724
60.8626545000000 -941.2370273047416
Update: Using @if…. approach:
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
# Intial candidates for fit, per FU: - thus, the E vs V input data has to be per FU
E0_init = -941.510817926696
V0_init = 63.54960592453
B0_init = 76.3746233515232
B0_prime_init = 4.05340727164527
def BM(x, a, b, c, d):
return a + b*x + c*x**2 + d*x**3
def devBM(x, b, c, d):
return b + 2*c*x + 3*d*x**2
# Data 1 (Red triangles):
V_C_I, E_C_I = np.loadtxt('./1.dat', skiprows = 1).T
# Data 14 (Empty grey triangles):
V_14, E_14 = np.loadtxt('./2.dat', skiprows = 1).T
init_vals = [E0_init, V0_init, B0_init, B0_prime_init]
popt_C_I, pcov_C_I = curve_fit(BM, V_C_I, E_C_I, p0=init_vals)
popt_14, pcov_14 = curve_fit(BM, V_14, E_14, p0=init_vals)
from scipy.optimize import fsolve
def equations(p):
x1, x2 = p
E1 = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - devBM(x2, popt_14[1], popt_14[2], popt_14[3])
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
return (E1, E2)
x1, x2 = fsolve(equations, (50, 60))
print 'x1 = ', x1
print 'x2 = ', x2
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
def comm_tangent(x, x1, slope_common_tangent):
return BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - slope_common_tangent * x1 + slope_common_tangent * x
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0], V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1], 10000)
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black', label='Cubic fit Calcite I' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b', label='Cubic fit Calcite II')
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='Calcite I', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='Calcite II', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
print 'devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) = ', devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
plt.ylim(-941.240, -941.225)
plt.ticklabel_format(useOffset=False)
plt.show()
I am able to find the common tangent, as shown below:
However, this common tangent corresponds to a common tangent in an area outside the data range, i.e., using
V_C_I_lin = np.linspace(V_C_I[0]-30, V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0]-20, V_14[-1]+2, 10000)
xp = np.linspace(40, 70, 100)
plt.ylim(-941.25, -941.18)
is possible to see the following:
Is it possible to constraint the solver to the range where we have data in order to find the desired common tangent?
Update 2.1: Using @if…. Range constraints approach, the following code yields x1 = 61.2569899
and x2 = 59.7677843
. If we plot it:
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import sys
from sympy import *
import sympy as sym
import os
# Intial candidates for fit, per FU: - thus, the E vs V input data has to be per FU
E0_init = -941.510817926696 # -1882.50963222/2.0
V0_init = 63.54960592453 #125.8532/2.0
B0_init = 76.3746233515232 #74.49
B0_prime_init = 4.05340727164527 #4.15
def BM(x, a, b, c, d):
return a + b*x + c*x**2 + d*x**3
def devBM(x, b, c, d):
return b + 2*c*x + 3*d*x**2
# Data 1 (Red triangles):
V_C_I, E_C_I = np.loadtxt('./1.dat', skiprows = 1).T
# Data 14 (Empty grey triangles):
V_14, E_14 = np.loadtxt('./2.dat', skiprows = 1).T
init_vals = [E0_init, V0_init, B0_init, B0_prime_init]
popt_C_I, pcov_C_I = curve_fit(BM, V_C_I, E_C_I, p0=init_vals)
popt_14, pcov_14 = curve_fit(BM, V_14, E_14, p0=init_vals)
def equations(p):
x1, x2 = p
E1 = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - devBM(x2, popt_14[1], popt_14[2], popt_14[3])
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
return (E1, E2)
from scipy.optimize import least_squares
lb = (61.0, 59.0) # lower bounds on x1, x2
ub = (62.0, 60.0) # upper bounds
result = least_squares(equations, [61, 59], bounds=(lb, ub))
print 'result = ', result
# The result obtained is:
# x1 = 61.2569899
# x2 = 59.7677843
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
def comm_tangent(x, x1, slope_common_tangent):
return BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - slope_common_tangent * x1 + slope_common_tangent * x
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0]-2, V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1]+2, 10000)
fig_handle = plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b' )
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='2', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
plt.ticklabel_format(useOffset=False)
plt.show()
We see that we are not obtaining a common tangent:
Answers:
Symbolic root finding
Your system of equations consists of a quadratic equation and a cubic equation. There is no closed-form symbolic solution of such a system. Indeed, if there was, one would be able to apply it to a general 5th degree equation x**5 + a*x**4 + ... = 0
by introducing y = x**2
(quadratic) and rewriting the original equation as x*y**2 + a*y**2 + ... = 0
(cubic). And we know that can’t be done. So it’s not surprising that SymPy can’t solve it. You need a numeric solver (another reason is that SymPy isn’t really designed to solve equations full of floating point constants, they are trouble for symbolic manipulations).
Numeric root finding
SciPy fsolve is the first thing that comes to mind. You could do something like this:
def F(x):
x1, x2 = x[0], x[1]
E1 = P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - P(x2, popt_14[1], popt_14[2], popt_14[3])
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
return [E1, E2]
print fsolve(F, [50, 60]) # some reasonable initial point
By the way, I would move (x1-x2) from the denominator in E2, rewriting E2 as
(...) - (x1 - x2) * P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
so the system is polynomial. This will likely make the life of fsolve
a little easier.
Range constraints: minimization
Neither fsolve
nor its relatives like root
support placing bounds on the variables. But you can use least_squares
which will look for the minimum of the sum of squares of expressions E1, E2. It supports upper and lower bounds, and with any luck, the minimum value (“cost”) will be 0 within machine precision, indicating you found a root. An abstract example (since I don’t have your data):
f1 = lambda x: 2*x**3 + 20
df1 = lambda x: 6*x**2 # derivative of f1.
f2 = lambda x: (x-3)**3 + x
df2 = lambda x: 3*(x-3)**2 + 1
def eqns(x):
x1, x2 = x[0], x[1]
eq1 = df1(x1) - df2(x2)
eq2 = df1(x1)*(x1 - x2) - (f1(x1) - f2(x2))
return [eq1, eq2]
from scipy.optimize import least_squares
lb = (2, -2) # lower bounds on x1, x2
ub = (5, 3) # upper bounds
least_squares(eqns, [3, 1], bounds=(lb, ub))
Output:
active_mask: array([0, 0])
cost: 2.524354896707238e-29
fun: array([7.10542736e-15, 0.00000000e+00])
grad: array([1.93525199e-13, 1.34611132e-13])
jac: array([[27.23625045, 18.94483256],
[66.10672633, -0. ]])
message: '`gtol` termination condition is satisfied.'
nfev: 8
njev: 8
optimality: 2.4802477446153134e-13
status: 1
success: True
x: array([ 2.26968753, -0.15747203])
The cost is very small, so we have a solution, and it is x. Typically, one assigns the output of least_squares
to some variable res
and accesses res.x
from there.
Thanks to all @if…. help, by running the below code posted at the end of this answer, the result of 3 pathways are discussed:
1) least_squares
with default tolerances:
#### ftol=1e-08, xtol=1e-08, gtol=1e-08 #####
result = active_mask: array([0, 0])
cost: 4.7190963603923405e-10
fun: array([ 1.60076424e-05, 2.62216448e-05])
grad: array([ -3.22762954e-09, -4.72137063e-09])
jac: array([[ 2.70753295e-04, -3.41257603e-04],
[ -2.88378229e-04, 2.82727898e-05]])
message: '`gtol` termination condition is satisfied.'
nfev: 4
njev: 4
optimality: 2.398161337354571e-09
status: 1
success: True
x: array([ 61.2569899, 59.7677843])
result.x = [ 61.2569899 59.7677843]
slope_common_tangent = -0.000533908881355
Cost is zero, and the common tangent found is very close, but not ideal:
2) least_squares
with tighter tolerances:
#### ftol=1e-12, xtol=1e-12, gtol=1e-12 #####
result_tight_tols = active_mask: array([0, 0])
cost: 4.3861335617475759e-20
fun: array([ 2.08437035e-10, 2.10420231e-10])
grad: array([ -5.40980234e-16, -7.19344843e-14])
jac: array([[ 2.69431398e-04, -3.45167744e-04],
[ -2.69462978e-04, 5.34965061e-08]])
message: '`gtol` termination condition is satisfied.'
nfev: 8
njev: 8
optimality: 7.6628451334260651e-15
status: 1
success: True
x: array([ 61.35744106, 59.89347466])
result_tight_tols.x = [ 61.35744106 59.89347466]
slope_common_tangent = -0.000506777791299
I was expecting the cost to be higher, but for some reason I do not understand, the cost is even lower. The common tangent found is much more closer:
3) Using fsolve
but restricting the region
We saw in the post that when using x1, x2 = fsolve(equations, (50, 60))
, the common tangent found was not the correct one (2nd and 3rd images). However, if we use x1, x2 = fsolve(equations, (61.5, 62))
:
#### Using `fsolve`, but restricting the region: ####
x1 = 61.3574418449
x2 = 59.8934758773
slope_common_tangent = -0.000506777580856
We see that the slope found is very similar to the least_squares
with tighter tolerances. Thus, the common tangent found is also very close:
This table summarises the results:
Code that produces these three pathways:
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import sys
from sympy import *
import sympy as sym
import os
import pickle as pl
# Intial candidates for fit, per FU: - thus, the E vs V input data has to be per FU
E0_init = -941.510817926696 # -1882.50963222/2.0
V0_init = 63.54960592453 #125.8532/2.0
B0_init = 76.3746233515232 #74.49
B0_prime_init = 4.05340727164527 #4.15
def BM(x, a, b, c, d):
return a + b*x + c*x**2 + d*x**3
def devBM(x, b, c, d):
return b + 2*c*x + 3*d*x**2
# Data 1 (Red triangles):
V_C_I, E_C_I = np.loadtxt('./1.dat', skiprows = 1).T
# Data 14 (Empty grey triangles):
V_14, E_14 = np.loadtxt('./2.dat', skiprows = 1).T
init_vals = [E0_init, V0_init, B0_init, B0_prime_init]
popt_C_I, pcov_C_I = curve_fit(BM, V_C_I, E_C_I, p0=init_vals)
popt_14, pcov_14 = curve_fit(BM, V_14, E_14, p0=init_vals)
def equations(p):
x1, x2 = p
E1 = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - devBM(x2, popt_14[1], popt_14[2], popt_14[3])
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
return (E1, E2)
from scipy.optimize import least_squares
lb = (61.0, 59.0) # lower bounds on x1, x2
ub = (62.0, 60.0) # upper bounds
result = least_squares(equations, [61, 59], bounds=(lb, ub))
result_tight_tols = least_squares(equations, [61, 59], ftol=1e-12, xtol=1e-12, gtol=1e-12, bounds=(lb, ub))
print """
#### ftol=1e-08, xtol=1e-08, gtol=1e-08 #####
"""
print 'result = ', result
print 'result.x = ', result.x
print """
"""
x1 = result.x[0]
x2 = result.x[1]
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
def comm_tangent(x, x1, slope_common_tangent):
return BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - slope_common_tangent * x1 + slope_common_tangent * x
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0]-2, V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1]+2, 10000)
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b' )
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='2', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
plt.title('Least squares. Default tolerances: ftol=1e-08, xtol=1e-08, gtol=1e-08')
plt.ticklabel_format(useOffset=False)
### Tighter tolerances:
print """
#### ftol=1e-12, xtol=1e-12, gtol=1e-12 #####
"""
print 'result_tight_tols = ', result_tight_tols
print 'result_tight_tols.x = ', result_tight_tols.x
print """
"""
x1 = result_tight_tols.x[0]
x2 = result_tight_tols.x[1]
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
def comm_tangent(x, x1, slope_common_tangent):
return BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - slope_common_tangent * x1 + slope_common_tangent * x
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0]-2, V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1]+2, 10000)
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b' )
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='2', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
plt.title('ftol=1e-08, xtol=1e-08, gtol=1e-08')
plt.ticklabel_format(useOffset=False)
plt.title('Lest Squares. Tightening tolerances: ftol=1e-12, xtol=1e-12, gtol=1e-12')
print """
#### Using `fsolve`, but restricting the region: ####
"""
from scipy.optimize import fsolve
x1, x2 = fsolve(equations, (61.5, 62))
print 'x1 = ', x1
print 'x2 = ', x2
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b' )
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='2', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
plt.ticklabel_format(useOffset=False)
plt.title('Using `fsolve`, but restricting the region')
plt.show()
I will assume some other people will come here not to find the solution to those functions. So I am eliminating the fitting part and providing a code that might be useful:
To find the common tangent of two functions of ‘x’, we need to find the points of intersection of their derivatives in ‘x’. Here is a Python function that accomplishes this task:
import sympy as sym
def find_common_tangent(f, g):
# Create symbolic variables
x = sym.symbols('x')
# Compute the derivatives of f and g
df = sym.diff(f, x)
dg = sym.diff(g, x)
# Find the points of intersection of the derivatives
points = sym.solve(df - dg)
# Return the points of intersection
return points
This function takes two functions f and g as arguments, and returns a list of points at which their derivatives intersect.
Note that this function uses the sympy library, which must be imported in order to use it.
To use this function, simply pass the two functions as arguments.
For example, to find the common tangent of the functions
# Define the functions f and g
x = sym.symbols('x')
f = x**2
g = x**3
# Find the common tangent of f and g
points = find_common_tangent(f, g)
# Print the points of intersection
print(points)
Given two functions, I would like to sort out the common tangent for both curves:
The slope of the common tangent can be obtained by the following:
slope of common tangent = (f(x1) - g(x2)) / (x1 - x2) = f'(x1) = g'(x2)
So that in the end we have a system of 2 equations with 2 unknowns:
f'(x1) = g'(x2) # Eq. 1
(f(x1) - g(x2)) / (x1 - x2) = f'(x1) # Eq. 2
For some reason I do not understand, python does not find the solution:
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
import sys
from sympy import *
import sympy as sym
# Intial candidates for fit
E0_init = -941.510817926696
V0_init = 63.54960592453
B0_init = 76.3746233515232
B0_prime_init = 4.05340727164527
# Data 1 (Red triangles):
V_C_I, E_C_I = np.loadtxt('./1.dat', skiprows = 1).T
# Data 14 (Empty grey triangles):
V_14, E_14 = np.loadtxt('./2.dat', skiprows = 1).T
def BM(x, a, b, c, d):
return (2.293710449E+17)*(1E-21)* (a + b*x + c*x**2 + d*x**3 )
def P(x, b, c, d):
return -b - 2*c*x - 3 *d*x**2
init_vals = [E0_init, V0_init, B0_init, B0_prime_init]
popt_C_I, pcov_C_I = curve_fit(BM, V_C_I, E_C_I, p0=init_vals)
popt_14, pcov_14 = curve_fit(BM, V_14, E_14, p0=init_vals)
x1 = var('x1')
x2 = var('x2')
E1 = P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - P(x2, popt_14[1], popt_14[2], popt_14[3])
print 'E1 = ', E1
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
sols = solve([E1, E2], [x1, x2])
print 'sols = ', sols
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0], V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1], 10000)
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black', label='Cubic fit data 1' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b', label='Cubic fit data 2')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='Data 1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='Data 2', s=100)
plt.ticklabel_format(useOffset=False)
plt.show()
1.dat
is the following:
61.6634100000000 -941.2375622594436
62.3429030000000 -941.2377748739724
62.9226515000000 -941.2378903605746
63.0043440000000 -941.2378981684135
63.7160150000000 -941.2378864590100
64.4085050000000 -941.2377753645115
65.1046835000000 -941.2375332100225
65.8049585000000 -941.2372030376584
66.5093925000000 -941.2367456992965
67.2180970000000 -941.2361992239395
67.9311515000000 -941.2355493856510
2.dat
is the following:
54.6569312500000 -941.2300821583739
55.3555152500000 -941.2312112888004
56.1392347500000 -941.2326135552780
56.9291575000000 -941.2338291772218
57.6992532500000 -941.2348135408652
58.4711572500000 -941.2356230099117
59.2666985000000 -941.2362715934311
60.0547935000000 -941.2367074271724
60.8626545000000 -941.2370273047416
Update: Using @if…. approach:
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
# Intial candidates for fit, per FU: - thus, the E vs V input data has to be per FU
E0_init = -941.510817926696
V0_init = 63.54960592453
B0_init = 76.3746233515232
B0_prime_init = 4.05340727164527
def BM(x, a, b, c, d):
return a + b*x + c*x**2 + d*x**3
def devBM(x, b, c, d):
return b + 2*c*x + 3*d*x**2
# Data 1 (Red triangles):
V_C_I, E_C_I = np.loadtxt('./1.dat', skiprows = 1).T
# Data 14 (Empty grey triangles):
V_14, E_14 = np.loadtxt('./2.dat', skiprows = 1).T
init_vals = [E0_init, V0_init, B0_init, B0_prime_init]
popt_C_I, pcov_C_I = curve_fit(BM, V_C_I, E_C_I, p0=init_vals)
popt_14, pcov_14 = curve_fit(BM, V_14, E_14, p0=init_vals)
from scipy.optimize import fsolve
def equations(p):
x1, x2 = p
E1 = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - devBM(x2, popt_14[1], popt_14[2], popt_14[3])
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
return (E1, E2)
x1, x2 = fsolve(equations, (50, 60))
print 'x1 = ', x1
print 'x2 = ', x2
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
def comm_tangent(x, x1, slope_common_tangent):
return BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - slope_common_tangent * x1 + slope_common_tangent * x
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0], V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1], 10000)
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black', label='Cubic fit Calcite I' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b', label='Cubic fit Calcite II')
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='Calcite I', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='Calcite II', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
print 'devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) = ', devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
plt.ylim(-941.240, -941.225)
plt.ticklabel_format(useOffset=False)
plt.show()
I am able to find the common tangent, as shown below:
However, this common tangent corresponds to a common tangent in an area outside the data range, i.e., using
V_C_I_lin = np.linspace(V_C_I[0]-30, V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0]-20, V_14[-1]+2, 10000)
xp = np.linspace(40, 70, 100)
plt.ylim(-941.25, -941.18)
is possible to see the following:
Is it possible to constraint the solver to the range where we have data in order to find the desired common tangent?
Update 2.1: Using @if…. Range constraints approach, the following code yields x1 = 61.2569899
and x2 = 59.7677843
. If we plot it:
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import sys
from sympy import *
import sympy as sym
import os
# Intial candidates for fit, per FU: - thus, the E vs V input data has to be per FU
E0_init = -941.510817926696 # -1882.50963222/2.0
V0_init = 63.54960592453 #125.8532/2.0
B0_init = 76.3746233515232 #74.49
B0_prime_init = 4.05340727164527 #4.15
def BM(x, a, b, c, d):
return a + b*x + c*x**2 + d*x**3
def devBM(x, b, c, d):
return b + 2*c*x + 3*d*x**2
# Data 1 (Red triangles):
V_C_I, E_C_I = np.loadtxt('./1.dat', skiprows = 1).T
# Data 14 (Empty grey triangles):
V_14, E_14 = np.loadtxt('./2.dat', skiprows = 1).T
init_vals = [E0_init, V0_init, B0_init, B0_prime_init]
popt_C_I, pcov_C_I = curve_fit(BM, V_C_I, E_C_I, p0=init_vals)
popt_14, pcov_14 = curve_fit(BM, V_14, E_14, p0=init_vals)
def equations(p):
x1, x2 = p
E1 = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - devBM(x2, popt_14[1], popt_14[2], popt_14[3])
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
return (E1, E2)
from scipy.optimize import least_squares
lb = (61.0, 59.0) # lower bounds on x1, x2
ub = (62.0, 60.0) # upper bounds
result = least_squares(equations, [61, 59], bounds=(lb, ub))
print 'result = ', result
# The result obtained is:
# x1 = 61.2569899
# x2 = 59.7677843
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
def comm_tangent(x, x1, slope_common_tangent):
return BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - slope_common_tangent * x1 + slope_common_tangent * x
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0]-2, V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1]+2, 10000)
fig_handle = plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b' )
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='2', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
plt.ticklabel_format(useOffset=False)
plt.show()
We see that we are not obtaining a common tangent:
Symbolic root finding
Your system of equations consists of a quadratic equation and a cubic equation. There is no closed-form symbolic solution of such a system. Indeed, if there was, one would be able to apply it to a general 5th degree equation x**5 + a*x**4 + ... = 0
by introducing y = x**2
(quadratic) and rewriting the original equation as x*y**2 + a*y**2 + ... = 0
(cubic). And we know that can’t be done. So it’s not surprising that SymPy can’t solve it. You need a numeric solver (another reason is that SymPy isn’t really designed to solve equations full of floating point constants, they are trouble for symbolic manipulations).
Numeric root finding
SciPy fsolve is the first thing that comes to mind. You could do something like this:
def F(x):
x1, x2 = x[0], x[1]
E1 = P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - P(x2, popt_14[1], popt_14[2], popt_14[3])
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
return [E1, E2]
print fsolve(F, [50, 60]) # some reasonable initial point
By the way, I would move (x1-x2) from the denominator in E2, rewriting E2 as
(...) - (x1 - x2) * P(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
so the system is polynomial. This will likely make the life of fsolve
a little easier.
Range constraints: minimization
Neither fsolve
nor its relatives like root
support placing bounds on the variables. But you can use least_squares
which will look for the minimum of the sum of squares of expressions E1, E2. It supports upper and lower bounds, and with any luck, the minimum value (“cost”) will be 0 within machine precision, indicating you found a root. An abstract example (since I don’t have your data):
f1 = lambda x: 2*x**3 + 20
df1 = lambda x: 6*x**2 # derivative of f1.
f2 = lambda x: (x-3)**3 + x
df2 = lambda x: 3*(x-3)**2 + 1
def eqns(x):
x1, x2 = x[0], x[1]
eq1 = df1(x1) - df2(x2)
eq2 = df1(x1)*(x1 - x2) - (f1(x1) - f2(x2))
return [eq1, eq2]
from scipy.optimize import least_squares
lb = (2, -2) # lower bounds on x1, x2
ub = (5, 3) # upper bounds
least_squares(eqns, [3, 1], bounds=(lb, ub))
Output:
active_mask: array([0, 0])
cost: 2.524354896707238e-29
fun: array([7.10542736e-15, 0.00000000e+00])
grad: array([1.93525199e-13, 1.34611132e-13])
jac: array([[27.23625045, 18.94483256],
[66.10672633, -0. ]])
message: '`gtol` termination condition is satisfied.'
nfev: 8
njev: 8
optimality: 2.4802477446153134e-13
status: 1
success: True
x: array([ 2.26968753, -0.15747203])
The cost is very small, so we have a solution, and it is x. Typically, one assigns the output of least_squares
to some variable res
and accesses res.x
from there.
Thanks to all @if…. help, by running the below code posted at the end of this answer, the result of 3 pathways are discussed:
1) least_squares
with default tolerances:
#### ftol=1e-08, xtol=1e-08, gtol=1e-08 #####
result = active_mask: array([0, 0])
cost: 4.7190963603923405e-10
fun: array([ 1.60076424e-05, 2.62216448e-05])
grad: array([ -3.22762954e-09, -4.72137063e-09])
jac: array([[ 2.70753295e-04, -3.41257603e-04],
[ -2.88378229e-04, 2.82727898e-05]])
message: '`gtol` termination condition is satisfied.'
nfev: 4
njev: 4
optimality: 2.398161337354571e-09
status: 1
success: True
x: array([ 61.2569899, 59.7677843])
result.x = [ 61.2569899 59.7677843]
slope_common_tangent = -0.000533908881355
Cost is zero, and the common tangent found is very close, but not ideal:
2) least_squares
with tighter tolerances:
#### ftol=1e-12, xtol=1e-12, gtol=1e-12 #####
result_tight_tols = active_mask: array([0, 0])
cost: 4.3861335617475759e-20
fun: array([ 2.08437035e-10, 2.10420231e-10])
grad: array([ -5.40980234e-16, -7.19344843e-14])
jac: array([[ 2.69431398e-04, -3.45167744e-04],
[ -2.69462978e-04, 5.34965061e-08]])
message: '`gtol` termination condition is satisfied.'
nfev: 8
njev: 8
optimality: 7.6628451334260651e-15
status: 1
success: True
x: array([ 61.35744106, 59.89347466])
result_tight_tols.x = [ 61.35744106 59.89347466]
slope_common_tangent = -0.000506777791299
I was expecting the cost to be higher, but for some reason I do not understand, the cost is even lower. The common tangent found is much more closer:
3) Using fsolve
but restricting the region
We saw in the post that when using x1, x2 = fsolve(equations, (50, 60))
, the common tangent found was not the correct one (2nd and 3rd images). However, if we use x1, x2 = fsolve(equations, (61.5, 62))
:
#### Using `fsolve`, but restricting the region: ####
x1 = 61.3574418449
x2 = 59.8934758773
slope_common_tangent = -0.000506777580856
We see that the slope found is very similar to the least_squares
with tighter tolerances. Thus, the common tangent found is also very close:
This table summarises the results:
Code that produces these three pathways:
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import sys
from sympy import *
import sympy as sym
import os
import pickle as pl
# Intial candidates for fit, per FU: - thus, the E vs V input data has to be per FU
E0_init = -941.510817926696 # -1882.50963222/2.0
V0_init = 63.54960592453 #125.8532/2.0
B0_init = 76.3746233515232 #74.49
B0_prime_init = 4.05340727164527 #4.15
def BM(x, a, b, c, d):
return a + b*x + c*x**2 + d*x**3
def devBM(x, b, c, d):
return b + 2*c*x + 3*d*x**2
# Data 1 (Red triangles):
V_C_I, E_C_I = np.loadtxt('./1.dat', skiprows = 1).T
# Data 14 (Empty grey triangles):
V_14, E_14 = np.loadtxt('./2.dat', skiprows = 1).T
init_vals = [E0_init, V0_init, B0_init, B0_prime_init]
popt_C_I, pcov_C_I = curve_fit(BM, V_C_I, E_C_I, p0=init_vals)
popt_14, pcov_14 = curve_fit(BM, V_14, E_14, p0=init_vals)
def equations(p):
x1, x2 = p
E1 = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3]) - devBM(x2, popt_14[1], popt_14[2], popt_14[3])
E2 = ((BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - BM(x2, popt_14[0], popt_14[1], popt_14[2], popt_14[3])) / (x1 - x2)) - devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
return (E1, E2)
from scipy.optimize import least_squares
lb = (61.0, 59.0) # lower bounds on x1, x2
ub = (62.0, 60.0) # upper bounds
result = least_squares(equations, [61, 59], bounds=(lb, ub))
result_tight_tols = least_squares(equations, [61, 59], ftol=1e-12, xtol=1e-12, gtol=1e-12, bounds=(lb, ub))
print """
#### ftol=1e-08, xtol=1e-08, gtol=1e-08 #####
"""
print 'result = ', result
print 'result.x = ', result.x
print """
"""
x1 = result.x[0]
x2 = result.x[1]
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
def comm_tangent(x, x1, slope_common_tangent):
return BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - slope_common_tangent * x1 + slope_common_tangent * x
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0]-2, V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1]+2, 10000)
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b' )
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='2', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
plt.title('Least squares. Default tolerances: ftol=1e-08, xtol=1e-08, gtol=1e-08')
plt.ticklabel_format(useOffset=False)
### Tighter tolerances:
print """
#### ftol=1e-12, xtol=1e-12, gtol=1e-12 #####
"""
print 'result_tight_tols = ', result_tight_tols
print 'result_tight_tols.x = ', result_tight_tols.x
print """
"""
x1 = result_tight_tols.x[0]
x2 = result_tight_tols.x[1]
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
def comm_tangent(x, x1, slope_common_tangent):
return BM(x1, popt_C_I[0], popt_C_I[1], popt_C_I[2], popt_C_I[3]) - slope_common_tangent * x1 + slope_common_tangent * x
# Linspace for plotting the fitting curves:
V_C_I_lin = np.linspace(V_C_I[0]-2, V_C_I[-1], 10000)
V_14_lin = np.linspace(V_14[0], V_14[-1]+2, 10000)
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b' )
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='2', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
plt.title('ftol=1e-08, xtol=1e-08, gtol=1e-08')
plt.ticklabel_format(useOffset=False)
plt.title('Lest Squares. Tightening tolerances: ftol=1e-12, xtol=1e-12, gtol=1e-12')
print """
#### Using `fsolve`, but restricting the region: ####
"""
from scipy.optimize import fsolve
x1, x2 = fsolve(equations, (61.5, 62))
print 'x1 = ', x1
print 'x2 = ', x2
slope_common_tangent = devBM(x1, popt_C_I[1], popt_C_I[2], popt_C_I[3])
print 'slope_common_tangent = ', slope_common_tangent
plt.figure()
# Plotting the fitting curves:
p2, = plt.plot(V_C_I_lin, BM(V_C_I_lin, *popt_C_I), color='black' )
p6, = plt.plot(V_14_lin, BM(V_14_lin, *popt_14), 'b' )
xp = np.linspace(54, 68, 100)
pcomm_tangent, = plt.plot(xp, comm_tangent(xp, x1, slope_common_tangent), 'green', label='Common tangent')
# Plotting the scattered points:
p1 = plt.scatter(V_C_I, E_C_I, color='red', marker="^", label='1', s=100)
p5 = plt.scatter(V_14, E_14, color='grey', marker="^", facecolors='none', label='2', s=100)
fontP = FontProperties()
fontP.set_size('13')
plt.legend((p1, p2, p5, p6, pcomm_tangent), ("1", "Cubic fit 1", "2", 'Cubic fit 2', 'Common tangent'), prop=fontP)
plt.ticklabel_format(useOffset=False)
plt.title('Using `fsolve`, but restricting the region')
plt.show()
I will assume some other people will come here not to find the solution to those functions. So I am eliminating the fitting part and providing a code that might be useful:
To find the common tangent of two functions of ‘x’, we need to find the points of intersection of their derivatives in ‘x’. Here is a Python function that accomplishes this task:
import sympy as sym
def find_common_tangent(f, g):
# Create symbolic variables
x = sym.symbols('x')
# Compute the derivatives of f and g
df = sym.diff(f, x)
dg = sym.diff(g, x)
# Find the points of intersection of the derivatives
points = sym.solve(df - dg)
# Return the points of intersection
return points
This function takes two functions f and g as arguments, and returns a list of points at which their derivatives intersect.
Note that this function uses the sympy library, which must be imported in order to use it.
To use this function, simply pass the two functions as arguments.
For example, to find the common tangent of the functions
# Define the functions f and g
x = sym.symbols('x')
f = x**2
g = x**3
# Find the common tangent of f and g
points = find_common_tangent(f, g)
# Print the points of intersection
print(points)