How to plot the slope (tangent line) of parabola at any point? - python

I want to plot a simple illustration of using derivative to find out a slope of a function at any point. It would look kinda like this:
I have already plotted a simple parabola using this code:
import numpy as np
from matplotlib import pyplot as plt
inputs = 0.2
weights = np.arange(-6,14)
target_prediction = 0.7
prediction = inputs*weights
errors = (prediction - target_prediction) ** 2
plt.xlabel("Weight")
plt.ylabel("Error")
plt.plot(weights, error)
Now I want to add something like this:
current_weight = 5
# draw a short fraction of a line to represent slope
x = np.arange(optimal_weight - 3, optimal_weight + 3)
# derivative
slope = 2 * (inputs*current_weight - target_prediction)
y = slope*x # How should this equation look like?
plt.plot(x, y)
To draw a tangent line going through the current_weight.
But I can't seem to figure this out, can you help?

Once you have the slope at the desired point, you need to write the equation for the tangent line using point-slope form:
# Define parabola
def f(x):
return x**2
# Define parabola derivative
def slope(x):
return 2*x
# Define x data range for parabola
x = np.linspace(-5,5,100)
# Choose point to plot tangent line
x1 = -3
y1 = f(x1)
# Define tangent line
# y = m*(x - x1) + y1
def line(x, x1, y1):
return slope(x1)*(x - x1) + y1
# Define x data range for tangent line
xrange = np.linspace(x1-1, x1+1, 10)
# Plot the figure
plt.figure()
plt.plot(x, f(x))
plt.scatter(x1, y1, color='C1', s=50)
plt.plot(xrange, line(xrange, x1, y1), 'C1--', linewidth = 2)
You can do this for any differentiable function, and can use derivative approximation methods (such as finite differencing) to eliminate the need to provide the analytical derivative.

Related

Two plots with different x ranges on the same figure, with sympy

I'm plotting the curve of a function, and it's tangent at point p. I would like to manage xlim for the curve and the tangent independently. In this code the tangent half-length should be 1:
from sympy import init_printing, symbols, N, plot
from sympy import diff
from sympy import log, cos, atan
init_printing()
x = symbols('x')
# Plot a tangent at point (p_x, p_y), of length l
def plot_line(p_x, p_y, x, a, l):
# Compute b, build tangent expression
b = p_y - a*p_x
t = a*x + b
# Limit line length
r = atan(a) # angle in rad
dx = N(l*cos(r)) # half range for x
lims = {'xlim': (p_x-dx, p_x+dx)}
# Build plot
t_plot = plot(t, show=False, **lims)
return t_plot
# Function
y = 2.1*log(x)
# Point
px = 7
py = y.subs(x, px)
# Plot curve and point
marker = {'args': [px, py, 'bo']}
lims = {'xlim': (0,10), 'ylim': (0,5)}
plots = plot(y, markers=[marker], show=False, **lims)
# Find derivative, plot tangent
y_d = diff(y)
a = y_d.subs(x, px)
plots.extend(plot_line(px, py, x, a, 1))
# Finalize and show plots
plots.aspect_ratio=(1,1)
plots.show()
However this is not the case...
SymPy's plot() function signature is something similar to this:
plot(expr, range, **kwargs)
where range is a 3-elements tuple: (symbol, min_val, max_val). The plot function will evaluate expr starting from min_val up to max_val.
One of the **kwargs is xlim, which is a 2-element tuple: xlim=(x_min, x_max). It is used to restrict the visualization along the x-axis from x_min to x_max. Nonetheless, the numerical values computed by the plot function go from min_val to max_val.
With that said, you need to remove xlim from inside plot_line and provide the range argument instead:
from sympy import init_printing, symbols, N, plot
from sympy import diff
from sympy import log, cos, atan
init_printing()
x = symbols('x')
# Plot a tangent at point (p_x, p_y), of length l
def plot_line(p_x, p_y, x, a, l):
# Compute b, build tangent expression
b = p_y - a*p_x
t = a*x + b
# Limit line length
r = atan(a) # angle in rad
dx = N(l*cos(r)) # half range for x
# Build plot
# Need to provide the range to limit the line length
t_plot = plot(t, (x, p_x-dx, p_x+dx), show=False)
return t_plot
# Function
y = 2.1*log(x)
# Point
px = 7
py = y.subs(x, px)
# Plot curve and point
marker = {'args': [px, py, 'bo']}
lims = {'xlim': (0,10), 'ylim': (0,5)}
plots = plot(y, markers=[marker], show=False, **lims)
# Find derivative, plot tangent
y_d = diff(y)
a = y_d.subs(x, px)
plots.extend(plot_line(px, py, x, a, 1))
# Finalize and show plots
plots.aspect_ratio=(1,1)
plots.show()

How to find crossection of hline and a function in python?

I have created a plot like in the figure below, red plot is original data, blue is a fitted function, the horizontal lines are different levels. I need to find the both intersection points with each line. do you have any suggestions ? Thanks In advance.
An easy way to do this numerically is to subtract the y-value of each horizontal line from your fit and then to solve the equation
fit(x) - y = 0 for x.
For this, scipy.optimize.fsolve can be used as follows (https://docs.scipy.org/doc/scipy/reference/generated/scipy.optimize.fsolve.html):
import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import fsolve # To find the zeros
from scipy.stats import cauchy
def my_fit(x):
# dummy function for this example
return cauchy().pdf(x)
horizontal_lines = [0.05, 0.15, 0.25]
colors = ['r', 'g', 'm']
x = np.linspace(-5, 5, 1000)
plt.plot(x, my_fit(x))
plt.hlines(horizontal_lines, -5, 5, ls="--", color=colors)
for i, y in enumerate(horizontal_lines):
x0 = fsolve(lambda x: my_fit(x) - y, -1)
x1 = fsolve(lambda x: my_fit(x) - y, 1)
print(f"Intersection points for {y=}: {x0=} {x1=}")
plt.scatter(x0, my_fit(x0), color=colors[i])
plt.scatter(x1, my_fit(x1), color=colors[i])
Output:
Intersection points for y=0.05: x0=array([-2.3165055]) x1=array([2.3165055])
Intersection points for y=0.15: x0=array([-1.05927612]) x1=array([1.05927612])
Intersection points for y=0.25: x0=array([-0.5227232]) x1=array([0.5227232])
A simple solution to this would be to just calculate the intersection points knowing the functions of the lines.
Example:
Line 1 : y = 2x
Line 2 : y = x^2
Intersection points:
2x = x^2
0 = x^2 - 2x
x1 = 2
x2 = 0
For y just substitute x in one of the functions,
y1 = 2x y1 = 2*(2) y1 = 4
y2 = 2x y2 = 2*(0) y2 = 0
Intersection point 1 of line 1 and line 2:
Itersection point 1: (2, 4)
Intersection point 2: (0,0)
enter image description here
Hope this helps.
The simplest non-mathematical solution would be:
take all points with y > y0
take the largest and smallest x from the remaining list.
The result is approximately correct for high point density.

Python how to control curvature when joining two points

I have a original curve. I am developing a model curve matching closely the original curve. Everything is working fine but not matching. How to control the curvature of my model curve? Below code is based on answer here.
My code:
def curve_line(point1, point2):
a = (point2[1] - point1[1])/(np.cosh(point2[0]) - np.cosh(point1[0]))
b = point1[1] - a*np.sinh(point1[0])
x = np.linspace(point1[0], point2[0],100).tolist()
y = (a*np.cosh(x) + b).tolist()
return x,y
###### A sample of my code is given below
point1 = [10,100]
point2 = [20,50]
x,y = curve_line(point1, point2)
plt.plot(point1[0], point1[1], 'o')
plt.plot(point2[0], point2[1], 'o')
plt.plot(x,y) ## len(x)
My present output:
I tried following function as well:
y = (50*np.exp(-x/10) +2.5)
The output is:
Instead of just guessing the right parameters of your model function, you can fit a model curve to your data using curve_fit.
import numpy as np
from scipy.optimize import curve_fit
import matplotlib.pyplot as plt
x = np.array([ 1.92, 14.35, 21.50, 25.27, 27.34, 30.32, 32.31, 34.09, 34.21])
y = np.array([8.30, 8.26, 8.13, 7.49, 6.66, 4.59, 2.66, 0.60, 0.06])
def fun(x, a, b, c):
return a * np.cosh(b * x) + c
coef,_ = curve_fit(fun, x, y)
plt.plot(x, y, label='Original curve')
plt.plot(x, fun(x, *coef), label=f'Model: %5.3f cosh(%4.2f x + %4.2f)' % tuple(coef) )
plt.legend()
plt.show()
If it is important that the start and end points are closely fitted, you can pass uncertainties to curve_fit, adjusting them to lower values towards the ends, e.g. by
s = np.ones(len(x))
s[1:-1] = s[1:-1] * 3
coef,_ = curve_fit(fun, x, y, sigma=s)
Your other approach a * np.exp(b * x) + c will also work and gives -0.006 exp(0.21 x + 8.49).
In some cases you'll have to provide an educated guess for the initial values of the coefficients to curve_fit (it uses 1 as default).

Solving 2nd order differential equations wrt this code

I cannot write the program which is solving 2nd order differential equation with respect to code I wrote for y'=y
I know that I should write a program which turn a 2nd order differential equation into two ordinary differential equations but I don!t know how can I do in Python.
P.S. : I have to use that code below. It's a homework
Please forgive my mistakes, it's my first question. Thanks in advance
from pylab import*
xd=[];y=[]
def F(x,y):
return y
def rk4(x0,y0,h,N):
xd.append(x0)
yd.append(y0)
for i in range (1,N+1) :
k1=F(x0,y0)
k2=F(x0+h/2,y0+h/2*k1)
k3=F(x0+h/2,y0+h/2*k2)
k4=F(x0+h,y0+h*k3)
k=1/6*(k1+2*k2+2*k3+k4)
y=y0+h*k
x=x0+h
yd.append(y)
xd.append(x)
y0=y
x0=x
return xd,yd
x0=0
y0=1
h=0.1
N=10
x,y=rk4(x0,y0,h,N)
print("x=",x)
print("y=",y)
plot(x,y)
show()
You can basically reformulate any scalar ODE (Ordinary Differential Equation) of order n in Cauchy form into an ODE of order 1. The only thing that you "pay" in this operation is that the second ODE's variables will be vectors instead of scalar functions.
Let me give you an example with an ODE of order 2. Suppose your ODE is: y'' = F(x,y, y'). Then you can replace it by [y, y']' = [y', F(x,y,y')], where the derivative of a vector has to be understood component-wise.
Let's take back your code and instead of using Runge-Kutta of order 4 as an approximate solution of your ODE, we will apply a simple Euler scheme.
from pylab import*
import matplotlib.pyplot as plt
# we are approximating the solution of y' = f(x,y) for x in [x_0, x_1] satisfying the Cauchy condition y(x_0) = y0
def f(x, y0):
return y0
# here f defines the equation y' = y
def explicit_euler(x0, x1, y0, N,):
# The following formula relates h and N
h = (x1 - x0)/(N+1)
xd = list()
yd = list()
xd.append(x0)
yd.append(y0)
for i in range (1,N+1) :
# We use the explicite Euler scheme y_{i+1} = y_i + h * f(x_i, y_i)
y = yd[-1] + h * f(xd[-1], yd[-1])
# you can replace the above scheme by any other (R-K 4 for example !)
x = xd[-1] + h
yd.append(y)
xd.append(x)
return xd, yd
N = 250
x1 = 5
x0 = 0
y0 = 1
# the only function which satisfies y(0) = 1 and y'=y is y(x)=exp(x).
xd, yd =explicit_euler(x0, x1, y0, N)
plt.plot(xd,yd)
plt.show()
# this plot has the right shape !
Note that you can replace the Euler scheme by R-K 4 which has better stability and convergence properties.
Now, suppose that you want to solve a second order ODE, let's say for example: y'' = -y with initial conditions y(0) = 1 and y'(0) = 0. Then you have to transform your scalar function y into a vector of size 2 as explained above and in the comments in code below.
from pylab import*
import matplotlib.pyplot as plt
import numpy as np
# we are approximating the solution of y'' = f(x,y,y') for x in [x_0, x_1] satisfying the Cauchy condition of order 2:
# y(x_0) = y0 and y'(x_0) = y1
def f(x, y_d_0, y_d_1):
return -y_d_0
# here f defines the equation y'' = -y
def explicit_euler(x0, x1, y0, y1, N,):
# The following formula relates h and N
h = (x1 - x0)/(N+1)
xd = list()
yd = list()
xd.append(x0)
# to allow group operations in R^2, we use the numpy library
yd.append(np.array([y0, y1]))
for i in range (1,N+1) :
# We use the explicite Euler scheme y_{i+1} = y_i + h * f(x_i, y_i)
# remember that now, yd is a list of vectors
# the equivalent order 1 equation is [y, y']' = [y', f(x,y,y')]
y = yd[-1] + h * np.array([yd[-1][1], f(xd[-1], yd[-1][0], yd[-1][1])]) # vector of dimension 2
print(y)
# you can replace the above scheme by any other (R-K 4 for example !)
x = xd[-1] + h # vector of dimension 1
yd.append(y)
xd.append(x)
return xd, yd
x0 = 0
x1 = 30
y0 = 1
y1 = 0
# the only function satisfying y(0) = 1, y'(0) = 0 and y'' = -y is y(x) = cos(x)
N = 5000
xd, yd =explicit_euler(x0, x1, y0, y1, N)
# I only want the first variable of yd
yd_1 = list(map(lambda y: y[0], yd))
plt.plot(xd,yd_1)
plt.show()

Plot arbitrary paths with constant width given in data coordinates

General aim
I am trying to write some plotting functionality that (at its core)
plots arbitrary paths with a constant width given in data coordinates
(i.e. unlike lines in matplotlib which have widths given in display coordinates).
Previous solutions
This answer achieves
the basic goal. However, this answer converts between display and data
coordinates and then uses a matplotlib line with adjusted
coordinates. The existing functionality in my code that I would like
to replace / extend inherits from matplotlib.patches.Polygon. Since
the rest of the code base makes extensive use of
matplotlib.patches.Polygon attributes and methods, I would like to
continue to inherit from that class.
Problem
My current implementation (code below) seems to come close. However,
the patch created by simple_test seems to be subtly thicker towards
the centre than it is at the start and end point, and I have no
explanation why that may be the case.
I suspect that the problem lies in the computation of the orthogonal vector.
As supporting evidence, I would like to point to the start and end points of the patch in the figure created by complicated_test, which do not seem exactly orthogonal to the path. However, the dot product of the orthonormal vector and the tangent vector is always zero, so I am not sure that what is going on here.
Output of simple_test:
Output of complicated_test:
Code
#!/usr/bin/env python
import numpy as np
import matplotlib.patches
import matplotlib.pyplot as plt
class CurvedPatch(matplotlib.patches.Polygon):
def __init__(self, path, width, *args, **kwargs):
vertices = self.get_vertices(path, width)
matplotlib.patches.Polygon.__init__(self, list(map(tuple, vertices)),
closed=True,
*args, **kwargs)
def get_vertices(self, path, width):
left = _get_parallel_path(path, -width/2)
right = _get_parallel_path(path, width/2)
full = np.concatenate([left, right[::-1]])
return full
def _get_parallel_path(path, delta):
# initialise output
offset = np.zeros_like(path)
# use the previous and the following point to
# determine the tangent at each point in the path;
for ii in range(1, len(path)-1):
offset[ii] += _get_shift(path[ii-1], path[ii+1], delta)
# handle start and end points
offset[0] = _get_shift(path[0], path[1], delta)
offset[-1] = _get_shift(path[-2], path[-1], delta)
return path + offset
def _get_shift(p1, p2, delta):
# unpack coordinates
x1, y1 = p1
x2, y2 = p2
# get orthogonal unit vector;
# adapted from https://stackoverflow.com/a/16890776/2912349
v = np.r_[x2-x1, y2-y1] # vector between points
v = v / np.linalg.norm(v) # unit vector
w = np.r_[-v[1], v[0]] # orthogonal vector
w = w / np.linalg.norm(w) # orthogonal unit vector
# check that vectors are indeed orthogonal
assert np.isclose(np.dot(v, w), 0.)
# rescale unit vector
dx, dy = delta * w
return dx, dy
def simple_test():
x = np.linspace(-1, 1, 1000)
y = np.sqrt(1. - x**2)
path = np.c_[x, y]
curve = CurvedPatch(path, 0.1, facecolor='red', alpha=0.5)
fig, ax = plt.subplots(1,1)
ax.add_artist(curve)
ax.plot(x, y) # plot path for reference
plt.show()
def complicated_test():
random_points = np.random.rand(10, 2)
# Adapted from https://stackoverflow.com/a/35007804/2912349
import scipy.interpolate as si
def scipy_bspline(cv, n=100, degree=3, periodic=False):
""" Calculate n samples on a bspline
cv : Array ov control vertices
n : Number of samples to return
degree: Curve degree
periodic: True - Curve is closed
"""
cv = np.asarray(cv)
count = cv.shape[0]
# Closed curve
if periodic:
kv = np.arange(-degree,count+degree+1)
factor, fraction = divmod(count+degree+1, count)
cv = np.roll(np.concatenate((cv,) * factor + (cv[:fraction],)),-1,axis=0)
degree = np.clip(degree,1,degree)
# Opened curve
else:
degree = np.clip(degree,1,count-1)
kv = np.clip(np.arange(count+degree+1)-degree,0,count-degree)
# Return samples
max_param = count - (degree * (1-periodic))
spl = si.BSpline(kv, cv, degree)
return spl(np.linspace(0,max_param,n))
x, y = scipy_bspline(random_points, n=1000).T
path = np.c_[x, y]
curve = CurvedPatch(path, 0.1, facecolor='red', alpha=0.5)
fig, ax = plt.subplots(1,1)
ax.add_artist(curve)
ax.plot(x, y) # plot path for reference
plt.show()
if __name__ == '__main__':
plt.ion()
simple_test()
complicated_test()

Categories