Intersection of two graphs in Python, find the x value - python

Let 0 <= x <= 1. I have two columns f and g of length 5000 respectively. Now I plot:
plt.plot(x, f, '-')
plt.plot(x, g, '*')
I want to find the point 'x' where the curve intersects. I don't want to find the intersection of f and g.
I can do it simply with:
set(f) & set(g)

You can use np.sign in combination with np.diff and np.argwhere to obtain the indices of points where the lines cross (in this case, the points are [ 0, 149, 331, 448, 664, 743]):
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 1000)
f = np.arange(0, 1000)
g = np.sin(np.arange(0, 10, 0.01) * 2) * 1000
plt.plot(x, f, '-')
plt.plot(x, g, '-')
idx = np.argwhere(np.diff(np.sign(f - g))).flatten()
plt.plot(x[idx], f[idx], 'ro')
plt.show()
First it calculates f - g and the corresponding signs using np.sign. Applying np.diff reveals all the positions, where the sign changes (e.g. the lines cross). Using np.argwhere gives us the exact indices.

For those who are using or open to use the Shapely library for geometry-related computations, getting the intersection will be much easier. You just have to construct LineString from each line and get their intersection as follows:
import numpy as np
import matplotlib.pyplot as plt
from shapely.geometry import LineString
x = np.arange(0, 1000)
f = np.arange(0, 1000)
g = np.sin(np.arange(0, 10, 0.01) * 2) * 1000
plt.plot(x, f)
plt.plot(x, g)
first_line = LineString(np.column_stack((x, f)))
second_line = LineString(np.column_stack((x, g)))
intersection = first_line.intersection(second_line)
if intersection.geom_type == 'MultiPoint':
plt.plot(*LineString(intersection).xy, 'o')
elif intersection.geom_type == 'Point':
plt.plot(*intersection.xy, 'o')
And to get the x and y values as NumPy arrays you would just write:
x, y = LineString(intersection).xy
# x: array('d', [0.0, 149.5724669847373, 331.02906176584617, 448.01182730277833, 664.6733061190541, 743.4822641140581])
# y: array('d', [0.0, 149.5724669847373, 331.02906176584617, 448.01182730277833, 664.6733061190541, 743.4822641140581])
or if an intersection is only one point:
x, y = intersection.xy

Here's a solution which:
Works with N-dimensional data
Uses Euclidean distance rather than merely finding cross-overs in the y-axis
Is more efficient with lots of data (it queries a KD-tree, which should query in logarathmic time instead of linear time).
You can change the distance_upper_bound in the KD-tree query to define how close is close enough.
You can query the KD-tree with many points at the same time, if needed. Note: if you need to query thousands of points at once, you can get dramatic performance increases by querying the KD-tree with another KD-tree.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.spatial import cKDTree
from scipy import interpolate
fig = plt.figure()
ax = fig.add_axes([0, 0, 1, 1], projection='3d')
ax.axis('off')
def upsample_coords(coord_list):
# s is smoothness, set to zero
# k is degree of the spline. setting to 1 for linear spline
tck, u = interpolate.splprep(coord_list, k=1, s=0.0)
upsampled_coords = interpolate.splev(np.linspace(0, 1, 100), tck)
return upsampled_coords
# target line
x_targ = [1, 2, 3, 4, 5, 6, 7, 8]
y_targ = [20, 100, 50, 120, 55, 240, 50, 25]
z_targ = [20, 100, 50, 120, 55, 240, 50, 25]
targ_upsampled = upsample_coords([x_targ, y_targ, z_targ])
targ_coords = np.column_stack(targ_upsampled)
# KD-tree for nearest neighbor search
targ_kdtree = cKDTree(targ_coords)
# line two
x2 = [3,4,5,6,7,8,9]
y2 = [25,35,14,67,88,44,120]
z2 = [25,35,14,67,88,44,120]
l2_upsampled = upsample_coords([x2, y2, z2])
l2_coords = np.column_stack(l2_upsampled)
# plot both lines
ax.plot(x_targ, y_targ, z_targ, color='black', linewidth=0.5)
ax.plot(x2, y2, z2, color='darkgreen', linewidth=0.5)
# find intersections
for i in range(len(l2_coords)):
if i == 0: # skip first, there is no previous point
continue
distance, close_index = targ_kdtree.query(l2_coords[i], distance_upper_bound=.5)
# strangely, points infinitely far away are somehow within the upper bound
if np.isinf(distance):
continue
# plot ground truth that was activated
_x, _y, _z = targ_kdtree.data[close_index]
ax.scatter(_x, _y, _z, 'gx')
_x2, _y2, _z2 = l2_coords[i]
ax.scatter(_x2, _y2, _z2, 'rx') # Plot the cross point
plt.show()

Well, I was looking for a matplotlib for two curves which were different in size and had not the same x values. Here is what I come up with:
import numpy as np
import matplotlib.pyplot as plt
import sys
fig = plt.figure()
ax = fig.add_subplot(111)
# x1 = [1,2,3,4,5,6,7,8]
# y1 = [20,100,50,120,55,240,50,25]
# x2 = [3,4,5,6,7,8,9]
# y2 = [25,200,14,67,88,44,120]
x1=[1.4,2.1,3,5.9,8,9,12,15]
y1=[2.3,3.1,1,3.9,8,9,11,9]
x2=[1,2,3,4,6,8,9,12,14]
y2=[4,12,7,1,6.3,7,5,6,11]
ax.plot(x1, y1, color='lightblue',linewidth=3, marker='s')
ax.plot(x2, y2, color='darkgreen', marker='^')
y_lists = y1[:]
y_lists.extend(y2)
y_dist = max(y_lists)/200.0
x_lists = x1[:]
x_lists.extend(x2)
x_dist = max(x_lists)/900.0
division = 1000
x_begin = min(x1[0], x2[0]) # 3
x_end = max(x1[-1], x2[-1]) # 8
points1 = [t for t in zip(x1, y1) if x_begin<=t[0]<=x_end] # [(3, 50), (4, 120), (5, 55), (6, 240), (7, 50), (8, 25)]
points2 = [t for t in zip(x2, y2) if x_begin<=t[0]<=x_end] # [(3, 25), (4, 35), (5, 14), (6, 67), (7, 88), (8, 44)]
# print points1
# print points2
x_axis = np.linspace(x_begin, x_end, division)
idx = 0
id_px1 = 0
id_px2 = 0
x1_line = []
y1_line = []
x2_line = []
y2_line = []
xpoints = len(x_axis)
intersection = []
while idx < xpoints:
# Iterate over two line segments
x = x_axis[idx]
if id_px1>-1:
if x >= points1[id_px1][0] and id_px1<len(points1)-1:
y1_line = np.linspace(points1[id_px1][1], points1[id_px1+1][1], 1000) # 1.4 1.401 1.402 etc. bis 2.1
x1_line = np.linspace(points1[id_px1][0], points1[id_px1+1][0], 1000)
id_px1 = id_px1 + 1
if id_px1 == len(points1):
x1_line = []
y1_line = []
id_px1 = -1
if id_px2>-1:
if x >= points2[id_px2][0] and id_px2<len(points2)-1:
y2_line = np.linspace(points2[id_px2][1], points2[id_px2+1][1], 1000)
x2_line = np.linspace(points2[id_px2][0], points2[id_px2+1][0], 1000)
id_px2 = id_px2 + 1
if id_px2 == len(points2):
x2_line = []
y2_line = []
id_px2 = -1
if x1_line!=[] and y1_line!=[] and x2_line!=[] and y2_line!=[]:
i = 0
while abs(x-x1_line[i])>x_dist and i < len(x1_line)-1:
i = i + 1
y1_current = y1_line[i]
j = 0
while abs(x-x2_line[j])>x_dist and j < len(x2_line)-1:
j = j + 1
y2_current = y2_line[j]
if abs(y2_current-y1_current)<y_dist and i != len(x1_line) and j != len(x2_line):
ymax = max(y1_current, y2_current)
ymin = min(y1_current, y2_current)
xmax = max(x1_line[i], x2_line[j])
xmin = min(x1_line[i], x2_line[j])
intersection.append((x, ymin+(ymax-ymin)/2))
ax.plot(x, y1_current, 'ro') # Plot the cross point
idx += 1
print "intersection points", intersection
plt.show()

Intersection probably occurs between points. Let's explore the example bellow.
import numpy as np
import matplotlib.pyplot as plt
xs=np.arange(0, 20)
y1=np.arange(0, 20)*2
y2=np.array([1, 1.5, 3, 8, 9, 20, 23, 21, 13, 23, 18, 20, 23, 24, 31, 28, 30, 33, 37, 36])
plotting the 2 curves above, along with their intersections, using as intersection the average coordinates before and after proposed from idx intersection, all points are closer to the first curve.
idx=np.argwhere(np.diff(np.sign(y1 - y2 )) != 0).reshape(-1) + 0
plt.plot(xs, y1)
plt.plot(xs, y2)
for i in range(len(idx)):
plt.plot((xs[idx[i]]+xs[idx[i]+1])/2.,(y1[idx[i]]+y1[idx[i]+1])/2., 'ro')
plt.legend(['Y1', 'Y2'])
plt.show()
using as intersection the average coordinates before and after but for both y1 and y2 curves usually are closer to true intersection
plt.plot(xs, y1)
plt.plot(xs, y2)
for i in range(len(idx)):
plt.plot((xs[idx[i]]+xs[idx[i]+1])/2.,(y1[idx[i]]+y1[idx[i]+1]+y2[idx[i]]+y2[idx[i]+1])/4., 'ro')
plt.legend(['Y1', 'Y2'])
plt.show()
For an even more accurate intersection estimation we could use interpolation.

For arrays f and g, we could simply do the following:
np.pad(np.diff(np.array(f > g).astype(int)), (1,0), 'constant', constant_values = (0,))
This will give the array of all the crossover points. Every 1 is a crossover from below to above and every -1 a crossover from above to below.

Even if f and g intersect, you cannot be sure that f[i]== g[i] for integer i (the intersection probably occurs between points).
You should instead test like
# detect intersection by change in sign of difference
d = f - g
for i in range(len(d) - 1):
if d[i] == 0. or d[i] * d[i + 1] < 0.:
# crossover at i
x_ = x[i]

I had a similar problem, but with one discontinue function, like the tangent function. To avoid get points on the discontinuity, witch i didn't want to consider a intersection, i added a tolerance parameter on the previous solutions that use np.diff and np.sign. I set the tolerance parameter as the mean of the differences between the two data points, witch suffices in my case.
import numpy as np
import matplotlib.pyplot as plt
fig,ax = plt.subplots(nrows = 1,ncols = 2)
x = np.arange(0, 1000)
f = 2*np.arange(0, 1000)
g = np.tan(np.arange(0, 10, 0.01) * 2) * 1000
#here we set a threshold to decide if we will consider that point as a intersection
tolerance = np.abs(np.diff(f-g)).mean()
idx = np.argwhere((np.diff(np.sign(f - g)) != 0) & (np.abs(np.diff(f-g)) <= tolerance)).flatten()
#general case (tolerance = infinity)
tolerance = np.inf
idx2 = np.argwhere((np.diff(np.sign(f - g)) != 0) & (np.abs(np.diff(f-g)) <= tolerance)).flatten()
ax1,ax2 = ax
ax1.plot(x,f); ax1.plot(x,g)
ax2.plot(x,f); ax2.plot(x,g)
ax1.plot(x[idx], f[idx], 'o'); ax1.set_ylim(-3000,3000)
ax2.plot(x[idx2],f[idx2], 'o'); ax2.set_ylim(-3000,3000)
plt.show()

As a documented and tested function (credit for the algorithm goes to #Matt, I only changed the example to something simpler and used linspace instead of arange to handle non-integers better):
from typing import Iterable, Tuple
import numpy as np
import doctest
def intersect(x: np.array, f: np.array, g: np.array) -> Iterable[Tuple[(int, int)]]:
"""
Finds the intersection points between `f` and `g` on the domain `x`.
Given:
- `x`: The discretized domain.
- `f`: The discretized values of the first function calculated on the
discretized domain.
- `g`: The discretized values of the second function calculated on the
discretized domain.
Returns:
An iterable containing the (x,y) points of intersection.
Test case, line-parabola intersection:
>>> x = np.linspace(0, 10, num=10000)
>>> f = 3 * x
>>> g = np.square(x)
>>> list(intersect(x, f, g))
[(0.0, 0.0), (2.999299929992999, 8.997899789978998)]
"""
idx = np.argwhere(np.diff(np.sign(f - g))).flatten()
return zip(x[idx], f[idx])
if __name__ == "__main__":
doctest.testmod()
In Python 2, just remove the type hints.

There may be multiple intersections, you can find the (x,y) point at every intersection by the following list comprehension
intersections = [(x[i], f[i]) for i,_ in enumerate(zip(f,g)) if f[i] == g[i]]
As a simple example
>>> x = [1,2,3,4,5]
>>> f = [2,4,6,8,10]
>>> g = [10,8,6,4,2]
>>> [(x[i], f[i]) for i,_ in enumerate(zip(f,g)) if f[i] == g[i]]
[(3, 6)]
So this found one intersection point at x = 3, y = 6. Note that if you are using float the two values may not be exactly equal, so you could use some tolerance instead of ==.

Related

Walk along path of discrete line segments evenly distributing points

I am trying to write a program that given a list of points indicating a path and given a desired number of marks, it should distribute these marks exactly evenly along the path. As it happens the path is cyclical but given an arbitrary point to both start and end at I don't think it affects the algorithm at all.
The first step is to sum up the length of the line segments to determine the total length of the path, and then dividing that by the number of marks to get the desired distance between marks. Easy enough.
The next step is to walk along the path, storing the coordinates of each mark each time you traverse another even multiple's worth of the distance between marks.
In my code, the traversal seems correct but the distribution of marks is not even and does not exactly follow the path. I have created a visualization in matplotlib to plot where the marks are landing showing this (see last section).
path data
point_data = [
(53.8024, 50.4762), (49.5272, 51.8727), (45.0118, 52.3863), (40.5399, 53.0184), (36.3951, 54.7708),
(28.7127, 58.6807), (25.5306, 61.4955), (23.3828, 65.2082), (22.6764, 68.3316), (22.6945, 71.535),
(24.6674, 77.6427), (28.8279, 82.4529), (31.5805, 84.0346), (34.7024, 84.8875), (45.9183, 84.5739),
(57.0529, 82.9846), (64.2141, 79.1657), (71.089, 74.802), (76.7944, 69.8429), (82.1092, 64.4783),
(83.974, 63.3605), (85.2997, 61.5455), (85.7719, 59.4206), (85.0764, 57.3729), (82.0979, 56.0247),
(78.878, 55.1062), (73.891, 53.0987), (68.7101, 51.7283), (63.6943, 51.2997), (58.6791, 51.7438),
(56.1255, 51.5243), (53.8024, 50.4762), (53.8024, 50.4762)]
traversal
import math
number_of_points = 20
def euclid_dist(x1, y1, x2, y2):
return ((x1-x2)**2 + (y1-y2)**2)**0.5
def move_point(x0, y0, d, theta_rad):
return x0 + d*math.cos(theta_rad), y0 + d*math.sin(theta_rad)
total_dist = 0
for i in range(1, len(point_data), 1):
x1, y1 = point_data[i - 1]
x2, y2 = point_data[i]
total_dist += euclid_dist(x1, y1, x2, y2)
dist_per_point = total_dist / number_of_points
length_left_over = 0 # distance left over from the last segment
# led_id = 0
results = []
for i in range(1, len(point_data), 1):
x1, y1 = point_data[i - 1]
x2, y2 = point_data[i]
angle_rads = math.atan2(y1-y2, x1-x2)
extra_rotation = math.pi / 2 # 90deg
angle_output = math.degrees((angle_rads + extra_rotation + math.pi) % (2*math.pi) - math.pi)
length_of_segment = euclid_dist(x1, y1, x2, y2)
distance_to_work_with = length_left_over + length_of_segment
current_dist = dist_per_point - length_left_over
while distance_to_work_with > dist_per_point:
new_point = move_point(x1, y1, current_dist, angle_rads)
results.append((new_point[0], new_point[1], angle_output))
current_dist += dist_per_point
distance_to_work_with -= dist_per_point
length_left_over = distance_to_work_with
visualization code
import matplotlib.pyplot as plt
from matplotlib import collections as mc
import numpy as np
X = np.array([x for x, _, _ in results])
Y = np.array([y for _, y, _ in results])
plt.scatter(X, Y)
for i, (x, y) in enumerate(zip(X, Y)):
plt.text(x, y, str(i), color="red", fontsize=12)
possible_colors = [(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1)]
lines = []
colors = []
for i in range(len(point_data) -1 , 0, -1):
x1, y1 = point_data[i - 1]
x2, y2 = point_data[i]
lines.append(((x1, y1), (x2, y2)))
colors.append(possible_colors[i % 3])
lc = mc.LineCollection(lines, colors = colors, linewidths=2)
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.margins(0.1)
plt.show()
visualization result
The key here is to find the segment on the path for each of the points we want to distribute along the path based on the cumulative distance (across segments) from the starting point on the path. Then, interpolate for the point based on the distance between the two end points of the segment in which the point is on the path. The following code does this using a mixture of numpy array processing and list comprehension:
point_data = [
(53.8024, 50.4762), (49.5272, 51.8727), (45.0118, 52.3863), (40.5399, 53.0184), (36.3951, 54.7708),
(28.7127, 58.6807), (25.5306, 61.4955), (23.3828, 65.2082), (22.6764, 68.3316), (22.6945, 71.535),
(24.6674, 77.6427), (28.8279, 82.4529), (31.5805, 84.0346), (34.7024, 84.8875), (45.9183, 84.5739),
(57.0529, 82.9846), (64.2141, 79.1657), (71.089, 74.802), (76.7944, 69.8429), (82.1092, 64.4783),
(83.974, 63.3605), (85.2997, 61.5455), (85.7719, 59.4206), (85.0764, 57.3729), (82.0979, 56.0247),
(78.878, 55.1062), (73.891, 53.0987), (68.7101, 51.7283), (63.6943, 51.2997), (58.6791, 51.7438),
(56.1255, 51.5243), (53.8024, 50.4762), (53.8024, 50.4762)
]
number_of_points = 20
def euclid_dist(x1, y1, x2, y2):
return ((x1-x2)**2 + (y1-y2)**2)**0.5
# compute distances between segment end-points (padded with 0. at the start)
# I am using the OP supplied function and list comprehension, but this
# can also be done using numpy
dist_between_points = [0.] + [euclid_dist(p0[0], p0[1], p1[0], p1[1])
for p0, p1 in zip(point_data[:-1], point_data[1:])]
cum_dist_to_point = np.cumsum(dist_between_points)
total_dist = sum(dist_between_points)
cum_dist_per_point = np.linspace(0., total_dist, number_of_points, endpoint=False)
# find the segment that the points will be in
point_line_segment_indices = np.searchsorted(cum_dist_to_point, cum_dist_per_point, side='right').astype(int)
# then do linear interpolation for the point based on distance between the two end points of the segment
# d0s: left end-point cumulative distances from start for segment containing point
# d1s: right end-point cumulative distances from start for segment containing point
# alphas: the interpolation distance in the segment
# p0s: left end-point for segment containing point
# p1s: right end-point for segment containing point
d0s = cum_dist_to_point[point_line_segment_indices - 1]
d1s = cum_dist_to_point[point_line_segment_indices]
alphas = (cum_dist_per_point - d0s) / (d1s - d0s)
p0s = [point_data[segment_index - 1] for segment_index in point_line_segment_indices]
p1s = [point_data[segment_index] for segment_index in point_line_segment_indices]
results = [(p0[0] + alpha * (p1[0] - p0[0]), p0[1] + alpha * (p1[1] - p0[1]))
for p0, p1, alpha in zip(p0s, p1s, alphas)]
The array cum_dist_to_point is the cumulative (across segments) distance along the path from the start to each point in point_data, and the array cum_dist_per_point is the cumulative distance along the path for the number of points we want to evenly distribute along the path. Note that we use np.searchsorted to identify the segment on the path (by cumulative distance from start) that the point, with a given distance from the start, lies in. According to the documentation, searchsorted:
Find the indices into a sorted array (first argument) such that, if the corresponding elements in the second argument were inserted before the indices, the order would be preserved.
Then, using the OP's plot function (slightly modified because results no longer has an angle component):
def plot_me(results):
X = np.array([x for x, _ in results])
Y = np.array([y for _, y in results])
plt.scatter(X, Y)
for i, (x, y) in enumerate(zip(X, Y)):
plt.text(x, y, str(i), color="red", fontsize=12)
possible_colors = [(1, 0, 0, 1), (0, 1, 0, 1), (0, 0, 1, 1)]
lines = []
colors = []
for i in range(len(point_data) -1, 0, -1):
x1, y1 = point_data[i - 1]
x2, y2 = point_data[i]
lines.append(((x1, y1), (x2, y2)))
colors.append(possible_colors[i % 3])
lc = mc.LineCollection(lines, colors=colors, linewidths=2)
fig, ax = plt.subplots()
ax.add_collection(lc)
ax.autoscale()
ax.margins(0.1)
plt.show()
We have:
plot_me(results)

graph functions with range in python

It is my first question on stackoverflow, if I am missing some data, they tell me, What I need is to create a function graph with the python language but I do not find information on how to do it, I find how to make the graphs but not the limits of the functions
What I need to graph in python is similar to this function with the ranges
This is the function
{ V₁x 0 ≤ x < a
{ V₁x - q(x-a)/2 a ≤ x ≤ a+l # probably it's q(x-a)²/2
M(x) = { V₂(L-x) a+l < x ≤ L
{ 0 otherwise
from matplotlib import pyplot as plt
x1 = [40, 50, 60, 70, 80, 90, 100]
y1 = [40, 50, 60, 70, 80, 90, 100]
plt.plot(x1, y1)
plt.xlabel('Like Geeks X Axis')
plt.ylabel('Like Geeks Y Axis')
axes = plt.axes()
axes.set_xlim([4, 8])
axes.set_ylim([-0.5, 2.5])
plt.show()
I think you can just create a function that reproduces your mathematical formulation and then get the ys using that function.
If you need your code to be generic then do it like this:
from matplotlib import pyplot as plt
def create_function(V1, V2, a, l, q, L):
def f(x):
if x >= 0 and x < a:
return V1 * x
elif x >= a and x <= a + l:
return V2 * x - q * (x - l)
elif x > a + l and x <= L:
return V2 * (L - x)
return 0
return f
f = create_function(1, 2, 1, 2, 0.5, 5)
x1 = list(range(15))
y1 = list(map(f, x1))
plt.plot(x1, y1)
plt.xlabel("Like Geeks X Axis")
plt.ylabel("Like Geeks Y Axis")
plt.show()
This way you have a generic function create_function that returns the function from your image with any parameter choice
If I understand what you are trying to do, plotting the diagram of the bending moment for a beam that is partially loaded with a constant distributed loading, here it is your graph
and here it is the code that produced it… but first a few preliminary remarks
if it's really the bending moment, there is a mistake in its definition, I took the liberty and corrected your definition
Matplotlib (and its brother Numpy) is not about manipulation of symbols (if you need that, look into Sympy) so all the quantities involved, q, l, a and L must be specified as numbers
that said, here it is the code
import matplotlib.pyplot as plt
def M(L, a, l, q, npoints=201):
# npoints is an OPTIONAL argument with a default value
from numpy import linspace, where
# we MATERIALIZE our x axis as a sequence of points, an array in Python-speak
x = linspace(0, L, npoints)
# compute the reactions
V2 = (l*q)*(a+l/2) / L
V1 = l*q-V2
# compute the bending moment,
# our where is similar to Excel "IF()" function, if you know Excel
bm = where(x<a, V1*x, where(x<a+l, V1*x - q*(x-a)**2/2, V2*(L-x)))
# the caller of this function wants to know the points in the arrays x and bm
return x, bm
# L = 10 units of length, a = 4, l = 2; q = 40 units of force per unit of length
x, bm = M(10, 4, 2, 40) # note: no optional npoints, 201 points will be used
fig, ax = plt.subplots()
ax.plot(x, bm)
# good mannered individuals plot the bending moment on the side of tension stresses
# so we invert the y axis
ax.invert_yaxis()
ax.grid()
plt.show()

Python: How to interpolate angles on a grid?

I have a grid with some given data. This data is given by its angle (from 0 to π).
Within this grid I have another smaller grid.
This might look like this:
Now I want to interpolate the angles on that grid.
I tried this by using scipy.interpolate.griddata what gives a good result. But there is a problem when the angles change from almost 0 to almost π (because the middle is π/2 ...)
Here is the result and it is easy to see what's going wrong.
How can I deal with that problem? Thank you! :)
Here is the code to reproduce:
import numpy as np
from matplotlib import pyplot as plt
from scipy.interpolate import griddata
ax = plt.subplot()
ax.set_aspect(1)
# Simulate some given data.
x, y = np.meshgrid(np.linspace(-10, 10, 20), np.linspace(-10, 10, 20))
data = np.arctan(y / 10) % np.pi
u = np.cos(data)
v = np.sin(data)
ax.quiver(x, y, u, v, headlength=0.01, headaxislength=0, pivot='middle', units='xy')
# Create a smaller grid within.
x1, y1 = np.meshgrid(np.linspace(-1, 5, 15), np.linspace(-6, 2, 20))
# ax.plot(x1, y1, '.', color='red', markersize=2)
# Interpolate data on grid.
interpolation = griddata((x.flatten(), y.flatten()), data.flatten(), (x1.flatten(), y1.flatten()))
u1 = np.cos(interpolation)
v1 = np.sin(interpolation)
ax.quiver(x1, y1, u1, v1, headlength=0.01, headaxislength=0, pivot='middle', units='xy',
color='red', scale=3, width=0.03)
plt.show()
Edit:
Thanks to #bubble, there is a way to adjust the given angles before interpolation such that the result will be as desired.
Therefore:
Define a rectifying function:
def RectifyData(data):
for j in range(len(data)):
step = data[j] - data[j - 1]
if abs(step) > np.pi / 2:
data[j] += np.pi * (2 * (step < 0) - 1)
return data
Interpolate as follows:
interpolation = griddata((x.flatten(), y.flatten()),
RectifyData(data.flatten()),
(x1.flatten(), y1.flatten()))
u1 = np.cos(interpolation)
v1 = np.sin(interpolation)
I tried direct interpolation of cos(angle) and sin(angle) values, but this still yielded to discontinues, that cause wrong line directions. The main idea consist in reducing discontinues, e.g. [2.99,3.01, 0.05,0.06] should be transformed to something like this: [2.99, 3.01, pi+0.05, pi+0.06]. This is needed to apply 2D interpolation algorithm correctly. Almost the same problem raises in the following post.
def get_rectified_angles(u, v):
angles = np.arcsin(v)
inds = u < 0
angles[inds] *= -1
# Direct approach of removing discontinues
# for j in range(len(angles[1:])):
# if abs(angles[j] - angles[j - 1]) > np.pi / 2:
# sel = [abs(angles[j] + np.pi - angles[j - 1]), abs(angles[j] - np.pi - angles[j-1])]
# if np.argmin(sel) == 0:
# angles[j] += np.pi
# else:
# angles[j] -= np.pi
return angles
ax.quiver(x, y, u, v, headlength=0.01, headaxislength=0, pivot='middle', units='xy')
# # Create a smaller grid within.
x1, y1 = np.meshgrid(np.linspace(-1, 5, 15), np.linspace(-6, 2, 20))
angles = get_rectified_angles(u.flatten(), v.flatten())
interpolation = griddata((x.flatten(), y.flatten()), angles, (x1.flatten(), y1.flatten()))
u1 = np.cos(interpolation)
v1 = np.sin(interpolation)
ax.quiver(x1, y1, u1, v1, headlength=0.01, headaxislength=0, pivot='middle', units='xy',
color='red', scale=3, width=0.03)
Probably, numpy.unwrap function could be used to fix discontinues. In case of 1d data, numpy.interp has keyword period to handle periodic data.

Graph velocity and distance from calculus functions in Python

Is there an more code-light way I could have implemented graphing the following piece-wise functions in the calculus problem more easily? In my approach, I used matplotlib and just combined the graphs into two main graphs to show the discontinuity.
import matplotlib.pyplot as plt
def v(time_range):
velocity_val = []
for i in time_range:
if i < .2:
velocity_val.append(20)
elif i > .2:
velocity_val.append(0)
return velocity_val
def f(time_range):
distance_val = []
for i in time_range:
if i <= .2:
distance_val.append(20*i)
if i >= .2:
distance_val.append(4)
return distance_val
def time_vals(time_range):
decimal = 100
time_val = []
for i in time_range:
num = i / decimal
time_val.append(num)
return time_val
#convert time into decimal
time_range_1 = range(1,20,1)
time_range_2 = range(21,40,1)
t_1 = time_vals(time_range_1)
t_2 = time_vals(time_range_2)
#get x, y for plot
v_1 = v(t_1)
v_2 = v(t_2)
f_1 = f(t_1)
f_2 = f(t_2)
#plot values into two graphs.
plt.subplot(2, 1, 1)
plt.plot(t_1, v_1)
plt.plot(t_2, v_2)
plt.title(' Problem 9')
plt.ylabel('Velocity')
plt.subplot(2, 1, 2)
plt.plot(t_1, f_1)
plt.plot(t_2, f_2)
plt.xlabel('time (t)')
plt.ylabel('Velocity');
You can use numpy to vectorize your code
Maybe something like:
# Define time steps
t = np.linspace(0, 1, 100)
# Build v
v = np.empty_like(t)
v[.2 < t] = 20
v[.2 >= t] = 0
# Build f
f = np.empty_like(t)
f[t < 0.2] = 20 * t[t < 0.2]
f[t >= 0.2] = 4
# Plot
plt.plot(t, v)
plt.plot(t, d)
Another way of building v and f is by using np.piecewise:
v = np.piecewise(t, [.2 < t, .2 >= t], [20, 0])
f = np.piecewise(t, [t <= .2, t > .2], [lambda x: 20 * x, 4])
I think np.piecewise is not very readable, but it definitely saves some lines of code
You can make use of np.where to assign your v(t) and f(t) depending on your the conditions. You don't need any for loops. Vectorized approaches make your code much neat and short. In np.where, you first check the condition and then the first value after the condition is assigned to the indices where the condition holds True and the second value is assigned to the indices where the condition holds False.
Below is an example:
import numpy as np
import matplotlib.pyplot as plt
# Initialise time
t_1 = np.arange(1,20,1)/100
t_2 = np.arange(21,40,1)/100
# Compute v(t)
v_1 = np.where(t_1<0.2, 20, 0)
v_2 = np.where(t_2<0.2, 20, 0)
# Compute f(t)
f_1 = np.where(t_1<=0.2, 20*t_1, 4)
f_2 = np.where(t_2<=0.2, 20*t_2, 4)
# Plotting as you are doing

Best way to interpolate a numpy.ndarray along an axis

I have 4-dimensional data, say for the temperature, in an numpy.ndarray.
The shape of the array is (ntime, nheight_in, nlat, nlon).
I have corresponding 1D arrays for each of the dimensions that tell me which time, height, latitude, and longitude a certain value corresponds to, for this example I need height_in giving the height in metres.
Now I need to bring it onto a different height dimension, height_out, with a different length.
The following seems to do what I want:
ntime, nheight_in, nlat, nlon = t_in.shape
nheight_out = len(height_out)
t_out = np.empty((ntime, nheight_out, nlat, nlon))
for time in range(ntime):
for lat in range(nlat):
for lon in range(nlon):
t_out[time, :, lat, lon] = np.interp(
height_out, height_in, t[time, :, lat, lon]
)
But with 3 nested loops, and lots of switching between python and numpy, I don't think this is the best way to do it.
Any suggestions on how to improve this? Thanks
scipy's interp1d can help:
import numpy as np
from scipy.interpolate import interp1d
ntime, nheight_in, nlat, nlon = (10, 20, 30, 40)
heights = np.linspace(0, 1, nheight_in)
t_in = np.random.normal(size=(ntime, nheight_in, nlat, nlon))
f_out = interp1d(heights, t_in, axis=1)
nheight_out = 50
new_heights = np.linspace(0, 1, nheight_out)
t_out = f_out(new_heights)
I was looking for a similar function that works with irregularly spaced coordinates, and ended up writing my own function. As far as I see, the interpolation is handled nicely and the performance in terms of memory and speed is also quite good. I thought I'd share it here in case anyone else comes across this question looking for a similar function:
import numpy as np
import warnings
def interp_along_axis(y, x, newx, axis, inverse=False, method='linear'):
""" Interpolate vertical profiles, e.g. of atmospheric variables
using vectorized numpy operations
This function assumes that the x-xoordinate increases monotonically
ps:
* Updated to work with irregularly spaced x-coordinate.
* Updated to work with irregularly spaced newx-coordinate
* Updated to easily inverse the direction of the x-coordinate
* Updated to fill with nans outside extrapolation range
* Updated to include a linear interpolation method as well
(it was initially written for a cubic function)
Peter Kalverla
March 2018
--------------------
More info:
Algorithm from: http://www.paulinternet.nl/?page=bicubic
It approximates y = f(x) = ax^3 + bx^2 + cx + d
where y may be an ndarray input vector
Returns f(newx)
The algorithm uses the derivative f'(x) = 3ax^2 + 2bx + c
and uses the fact that:
f(0) = d
f(1) = a + b + c + d
f'(0) = c
f'(1) = 3a + 2b + c
Rewriting this yields expressions for a, b, c, d:
a = 2f(0) - 2f(1) + f'(0) + f'(1)
b = -3f(0) + 3f(1) - 2f'(0) - f'(1)
c = f'(0)
d = f(0)
These can be evaluated at two neighbouring points in x and
as such constitute the piecewise cubic interpolator.
"""
# View of x and y with axis as first dimension
if inverse:
_x = np.moveaxis(x, axis, 0)[::-1, ...]
_y = np.moveaxis(y, axis, 0)[::-1, ...]
_newx = np.moveaxis(newx, axis, 0)[::-1, ...]
else:
_y = np.moveaxis(y, axis, 0)
_x = np.moveaxis(x, axis, 0)
_newx = np.moveaxis(newx, axis, 0)
# Sanity checks
if np.any(_newx[0] < _x[0]) or np.any(_newx[-1] > _x[-1]):
# raise ValueError('This function cannot extrapolate')
warnings.warn("Some values are outside the interpolation range. "
"These will be filled with NaN")
if np.any(np.diff(_x, axis=0) < 0):
raise ValueError('x should increase monotonically')
if np.any(np.diff(_newx, axis=0) < 0):
raise ValueError('newx should increase monotonically')
# Cubic interpolation needs the gradient of y in addition to its values
if method == 'cubic':
# For now, simply use a numpy function to get the derivatives
# This produces the largest memory overhead of the function and
# could alternatively be done in passing.
ydx = np.gradient(_y, axis=0, edge_order=2)
# This will later be concatenated with a dynamic '0th' index
ind = [i for i in np.indices(_y.shape[1:])]
# Allocate the output array
original_dims = _y.shape
newdims = list(original_dims)
newdims[0] = len(_newx)
newy = np.zeros(newdims)
# set initial bounds
i_lower = np.zeros(_x.shape[1:], dtype=int)
i_upper = np.ones(_x.shape[1:], dtype=int)
x_lower = _x[0, ...]
x_upper = _x[1, ...]
for i, xi in enumerate(_newx):
# Start at the 'bottom' of the array and work upwards
# This only works if x and newx increase monotonically
# Update bounds where necessary and possible
needs_update = (xi > x_upper) & (i_upper+1<len(_x))
# print x_upper.max(), np.any(needs_update)
while np.any(needs_update):
i_lower = np.where(needs_update, i_lower+1, i_lower)
i_upper = i_lower + 1
x_lower = _x[[i_lower]+ind]
x_upper = _x[[i_upper]+ind]
# Check again
needs_update = (xi > x_upper) & (i_upper+1<len(_x))
# Express the position of xi relative to its neighbours
xj = (xi-x_lower)/(x_upper - x_lower)
# Determine where there is a valid interpolation range
within_bounds = (_x[0, ...] < xi) & (xi < _x[-1, ...])
if method == 'linear':
f0, f1 = _y[[i_lower]+ind], _y[[i_upper]+ind]
a = f1 - f0
b = f0
newy[i, ...] = np.where(within_bounds, a*xj+b, np.nan)
elif method=='cubic':
f0, f1 = _y[[i_lower]+ind], _y[[i_upper]+ind]
df0, df1 = ydx[[i_lower]+ind], ydx[[i_upper]+ind]
a = 2*f0 - 2*f1 + df0 + df1
b = -3*f0 + 3*f1 - 2*df0 - df1
c = df0
d = f0
newy[i, ...] = np.where(within_bounds, a*xj**3 + b*xj**2 + c*xj + d, np.nan)
else:
raise ValueError("invalid interpolation method"
"(choose 'linear' or 'cubic')")
if inverse:
newy = newy[::-1, ...]
return np.moveaxis(newy, 0, axis)
And this is a small example to test it:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d as scipy1d
# toy coordinates and data
nx, ny, nz = 25, 30, 10
x = np.arange(nx)
y = np.arange(ny)
z = np.tile(np.arange(nz), (nx,ny,1)) + np.random.randn(nx, ny, nz)*.1
testdata = np.random.randn(nx,ny,nz) # x,y,z
# Desired z-coordinates (must be between bounds of z)
znew = np.tile(np.linspace(2,nz-2,50), (nx,ny,1)) + np.random.randn(nx, ny, 50)*0.01
# Inverse the coordinates for testing
z = z[..., ::-1]
znew = znew[..., ::-1]
# Now use own routine
ynew = interp_along_axis(testdata, z, znew, axis=2, inverse=True)
# Check some random profiles
for i in range(5):
randx = np.random.randint(nx)
randy = np.random.randint(ny)
checkfunc = scipy1d(z[randx, randy], testdata[randx,randy], kind='cubic')
checkdata = checkfunc(znew)
fig, ax = plt.subplots()
ax.plot(testdata[randx, randy], z[randx, randy], 'x', label='original data')
ax.plot(checkdata[randx, randy], znew[randx, randy], label='scipy')
ax.plot(ynew[randx, randy], znew[randx, randy], '--', label='Peter')
ax.legend()
plt.show()
Following the criteria of numpy.interp, one can assign the left/right bounds to the points outside the range adding this lines after within_bounds = ...
out_lbound = (xi <= _x[0,...])
out_rbound = (_x[-1,...] <= xi)
and
newy[i, out_lbound] = _y[0, out_lbound]
newy[i, out_rbound] = _y[-1, out_rbound]
after newy[i, ...] = ....
If I understood well the strategy used by #Peter9192, I think the changes are in the same line. I've checked a little bit, but maybe some strange case could not work properly.

Categories

Resources