Angles calculated by function are inconsistent - python

I have a function that is intended to rotate polygons by 5 degrees left or right and return their new points. This function is as follows, along with the function player_center that it requires.
# finds center of player shape
# finds slope and midpoint of each vertice-midpoint line on the longer sides,
# then the intercept of them all
def player_center(player):
left_mid = line_midpoint(player[0], player[1])
right_mid = line_midpoint(player[0], player[2])
left_mid_slope = line_slope(left_mid, player[2])
right_mid_slope = line_slope(right_mid, player[1])
left_mid_line = find_equation(player[2], left_mid_slope, True)
right_mid_line = find_equation(player[1], right_mid_slope, True)
standard_left_mid_line = slope_intercept_to_standard(left_mid_line[0], left_mid_line[1], left_mid_line[2])
standard_right_mid_line = slope_intercept_to_standard(right_mid_line[0], right_mid_line[1], right_mid_line[2])
lines = sym.Matrix([standard_left_mid_line, standard_right_mid_line])
return (float(lines.rref()[0].row(0).col(2)[0]), float(lines.rref()[0].row(1).col(2)[0]))
# rotates the player using SOHCAHTOA
# divides x coordinate by radius to find angle, then adds or subtracts increment of 5 to it depending on direction
# calculates the position of point at incremented angle, then appends to new set of points
# finally, new set is returned
# direction; 1 is left, 0 is right
def rotate_player(player, direction):
increment = math.pi/36 # radian equivalent of 5 degrees
full_circle = 2 * math.pi # radian equivalent of 360 degrees
center = player_center(player)
new_player = []
for point in player:
radius = line_distance(point, center)
point_sin = (center[1] - point[1])/radius
while (point_sin > 1):
point_sin -= 1
point_angle = math.asin(point_sin)
if (direction == 1):
if ((point_angle+increment) > math.pi * 2):
new_angle = (point_angle+increment) - math.pi * 2
else:
new_angle = point_angle + increment
else:
if ((point_angle-increment) < 0):
new_angle = 2 * math.pi + (point_angle-increment)
else:
new_angle = point_angle-increment
print("The angle was {}".format(math.degrees(point_angle)))
print("The angle is now {}".format(math.degrees(new_angle))) # print lines are for debug purposes
new_point = ((radius * math.cos(new_angle)) + center[0], -(radius * math.sin(new_angle)) + center[1])
new_player.append(new_point)
print(new_player)
return new_player
The geometric functions that it relies on are all defined in this file here:
import math
import sympy as sym
# shape is in form of list of tuples e.g [(1,1), (2,1), (1,0), (2,0)]
# angle is in degrees
# def rotate_shape(shape, angle):
def line_distance(first_point, second_point):
return math.sqrt( (second_point[0] - first_point[0]) ** 2 + (second_point[1] - first_point[1]) ** 2)
# undefined is represented by None in this program
def line_slope(first_point, second_point):
if (second_point[0] - first_point[0] == 0):
return None
elif (second_point[1] - first_point[1] == 0):
return 0
else:
return (second_point[1] - first_point[1])/(second_point[0] - first_point[0])
def line_midpoint(first_point, second_point):
return ( (first_point[0] + second_point[0])/2, (first_point[1] + second_point[1])/2 )
def find_equation(coord_pair, slope, array_form):
# Array form useful for conversion into standard form
if (array_form == True):
if (slope == 0):
intercept = coord_pair[1]
return [0, 1, intercept]
elif (slope == None):
intercept = coord_pair[0]
return [1, 0, intercept]
else:
intercept = coord_pair[1] - (coord_pair[0] * slope)
return [slope, 1, intercept]
else:
if (slope == 0):
intercept = coord_pair[1]
print("y = {0}".format(intercept))
return
elif (slope == None):
intercept = coord_pair[0]
print("x = {0}".format(intercept))
return
else:
intercept = coord_pair[1] - (coord_pair[0] * slope)
if (intercept >= 0):
print("y = {0}x + {1}".format(slope, intercept))
return
else:
print("y = {0}x - {1}".format(slope, intercept))
def find_perpendicular(slope):
if (slope == 0):
return None
elif (slope == None):
return 0
else:
return -(1/slope)
def find_perp_bisector(first_point, second_point):
# This function finds the perpendicular bisector between two points, using funcs made previously
midpoint = line_midpoint(first_point, second_point)
slope = line_slope(first_point, second_point)
return find_equation(midpoint, -(1/slope))
def find_perp_equation(x, y, m, array_form):
# returns the perpendicular equation of a given line
if (array_form == True):
return [find_perpendicular(x), y, m]
else:
if (m >= 0):
print("{0}y = {1}x + {2}".format(y, find_perpendicular(x), m))
else:
print("{0}y = {1}x - {2}".format(y, find_perpendicular(x), m))
def find_hyp(a, b):
return math.sqrt((a**2) + (b**2))
def find_tri_area(a, b, c):
# finds area of triangle using Heron's formula
semi = (a+b+c)/2
return math.sqrt(semi * (semi - a) * (semi - b) * (semi - c) )
def r_tri_check(a, b, c):
if (a**2) + (b**2) != (c**2):
print("This thing fake, bro.")
def find_point_section(first_point, second_point, ratio):
# I coded this half a year ago and can't remember what for, but kept it here anyway.
# separtions aren't necessary, but good for code readability
first_numerator = (ratio[0] * second_point[0]) + (ratio[1] * first_point[0])
second_numerator = (ratio[0] * second_point[1]) + (ratio[1] * first_point[1])
return ( first_numerator/(ratio[0]+ratio[1]), second_numerator/(ratio[0] + ratio[1]))
def slope_intercept_to_standard(x, y, b):
# x and y are the coeffients of the said variables, for example 5y = 3x + 8 would be inputted as (3, 5, 8)
if (x == 0):
return [0, 1, b]
elif (y == 0):
return [x, 0, b]
else:
return [x, -y, -b]
It mathematically seems sound, but when I try to apply it, all hell breaks loose.
For example when trying to apply it with the set polygon_points equal to [(400, 300), (385, 340), (415, 340)], All hell breaks loose.
An example of the output among repeated calls to the function upon polygon_points(outputs manually spaced for clarity):
The angle was 90.0
The angle is now 95.0
The angle was -41.633539336570394
The angle is now -36.633539336570394
The angle was -41.63353933657017
The angle is now -36.63353933657017
The angle was 64.4439547804165
The angle is now 69.4439547804165
The angle was -64.44395478041695
The angle is now -59.44395478041695
The angle was -64.44395478041623
The angle is now -59.44395478041624
The angle was 80.94458887142648
The angle is now 85.94458887142648
The angle was -80.9445888714264
The angle is now -75.9445888714264
The angle was -80.94458887142665
The angle is now -75.94458887142665
Can anyone explain this?

Too much irrelevant code, a lot of magic like this while (point_sin > 1): point_sin -= 1 - so hard to reproduce.
To rotate point around some center, you need just this (where cos(fi), sin(fi) are precalculated value in your case):
new_x = center_x + (old_x - center_x) * cos(fi) - (old_y - center_y) * sin(fi)
new_y = center_y + (old_x - center_x) * sin(fi) + (old_y - center_y) * cos(fi)

This is a built-in capability of RegularPolygon in SymPy:
>>> from sympy import RegularPolygon, rad
>>> p = RegularPolygon((0,0), 1, 5)
>>> p.vertices[0]
Point2D(1, 0)
>>> p.rotate(rad(30)) # or rad(-30)
>>> p.vertices[0]
Point2D(sqrt(3)/2, 1/2)

Related

How to vectorize a nested "for" loop with multiple "if" statements using Numpy?

I have a simple 2D ray-casting routine that gets terribly slow as soon as the number of obstacles increases.
This routine is made up of:
2 for loops (outer loop iterates over each ray/direction, then inner loop iterates over each line obstacle)
multiple if statements (check if a value is > or < than another value or if an array is empty)
Question: How can I condense all these operations into 1 single block of vectorized instructions using Numpy ?
More specifically, I am facing 2 issues:
I have managed to vectorize the inner loop (intersection between a ray and each obstacle) but I am unable to run this operation for all rays at once.
The only workaround I found to deal with the if statements is to use masked arrays. Something tells me it is not the proper way to handle these statements in this case (it seems clumsy, cumbersome and unpythonic)
Original code:
from math import radians, cos, sin
import matplotlib.pyplot as plt
import numpy as np
N = 10 # dimensions of canvas (NxN)
sides = np.array([[0, N, 0, 0], [0, N, N, N], [0, 0, 0, N], [N, N, 0, N]])
edges = np.random.rand(5, 4) * N # coordinates of 5 random segments (x1, x2, y1, y2)
edges = np.concatenate((edges, sides))
center = np.array([N/2, N/2]) # coordinates of center point
directions = np.array([(cos(radians(a)), sin(radians(a))) for a in range(0, 360, 10)]) # vectors pointing in all directions
intersections = []
# for each direction
for d in directions:
min_dist = float('inf')
# for each edge
for e in edges:
p1x, p1y = e[0], e[2]
p2x, p2y = e[1], e[3]
p3x, p3y = center
p4x, p4y = center + d
# find intersection point
den = (p1x - p2x) * (p3y - p4y) - (p1y - p2y) * (p3x - p4x)
if den:
t = ((p1x - p3x) * (p3y - p4y) - (p1y - p3y) * (p3x - p4x)) / den
u = -((p1x - p2x) * (p1y - p3y) - (p1y - p2y) * (p1x - p3x)) / den
# if any:
if t > 0 and t < 1 and u > 0:
sx = p1x + t * (p2x - p1x)
sy = p1y + t * (p2y - p1y)
isec = np.array([sx, sy])
dist = np.linalg.norm(isec-center)
# make sure to select the nearest one (from center)
if dist < min_dist:
min_dist = dist
nearest = isec
# store nearest interesection point for each ray
intersections.append(nearest)
# Render
plt.axis('off')
for x, y in zip(edges[:,:2], edges[:,2:]):
plt.plot(x, y)
for isec in np.array(intersections):
plt.plot((center[0], isec[0]), (center[1], isec[1]), '--', color="#aaaaaa", linewidth=.8)
Vectorized version (attempt):
from math import radians, cos, sin
import matplotlib.pyplot as plt
from scipy import spatial
import numpy as np
N = 10 # dimensions of canvas (NxN)
sides = np.array([[0, N, 0, 0], [0, N, N, N], [0, 0, 0, N], [N, N, 0, N]])
edges = np.random.rand(5, 4) * N # coordinates of 5 random segments (x1, x2, y1, y2)
edges = np.concatenate((edges, sides))
center = np.array([N/2, N/2]) # coordinates of center point
directions = np.array([(cos(radians(a)), sin(radians(a))) for a in range(0, 360, 10)]) # vectors pointing in all directions
intersections = []
# Render edges
plt.axis('off')
for x, y in zip(edges[:,:2], edges[:,2:]):
plt.plot(x, y)
# for each direction
for d in directions:
p1x, p1y = edges[:,0], edges[:,2]
p2x, p2y = edges[:,1], edges[:,3]
p3x, p3y = center
p4x, p4y = center + d
# denominator
den = (p1x - p2x) * (p3y - p4y) - (p1y - p2y) * (p3x - p4x)
# first 'if' statement -> if den > 0
mask = den > 0
den = den[mask]
p1x = p1x[mask]
p1y = p1y[mask]
p2x = p2x[mask]
p2y = p2y[mask]
t = ((p1x - p3x) * (p3y - p4y) - (p1y - p3y) * (p3x - p4x)) / den
u = -((p1x - p2x) * (p1y - p3y) - (p1y - p2y) * (p1x - p3x)) / den
# second 'if' statement -> if (t>0) & (t<1) & (u>0)
mask2 = (t > 0) & (t < 1) & (u > 0)
t = t[mask2]
p1x = p1x[mask2]
p1y = p1y[mask2]
p2x = p2x[mask2]
p2y = p2y[mask2]
# x, y coordinates of all intersection points in the current direction
sx = p1x + t * (p2x - p1x)
sy = p1y + t * (p2y - p1y)
pts = np.c_[sx, sy]
# if any:
if pts.size > 0:
# find nearest intersection point
tree = spatial.KDTree(pts)
nearest = pts[tree.query(center)[1]]
# Render
plt.plot((center[0], nearest[0]), (center[1], nearest[1]), '--', color="#aaaaaa", linewidth=.8)
Reformulation of the problem – Finding the intersection between a line segment and a line ray
Let q and q2 be the endpoints of a segment (obstacle). For convenience let's define a class to represent points and vectors in the plane. In addition to the usual operations, a vector multiplication is defined by u × v = u.x * v.y - u.y * v.x.
Caution: here Coord(2, 1) * 3 returns Coord(6, 3) while Coord(2, 1) * Coord(-1, 4) outputs 9. To avoid this confusion it might have been possible to restrict * to the scalar multiplication and use ^ via __xor__ for the vector multiplication.
class Coord:
def __init__(self, x, y):
self.x = x
self.y = y
#property
def radius(self):
return np.sqrt(self.x ** 2 + self.y ** 2)
def _cross_product(self, other):
assert isinstance(other, Coord)
return self.x * other.y - self.y * other.x
def __mul__(self, other):
if isinstance(other, Coord):
# 2D "cross"-product
return self._cross_product(other)
elif isinstance(other, int) or isinstance(other, float):
# scalar multiplication
return Coord(self.x * other, self.y * other)
def __rmul__(self, other):
return self * other
def __sub__(self, other):
return Coord(self.x - other.x, self.y - other.y)
def __add__(self, other):
return Coord(self.x + other.x, self.y + other.y)
def __repr__(self):
return f"Coord({self.x}, {self.y})"
Now, I find it easier to handle a ray in polar coordinates: For a given angle theta (direction) the goal is to determine if it intersects the segment, and if so determine the corresponding radius. Here is a function to find that. See here for an explanation of why and how. I tried to use the same variable names as in the previous link.
def find_intersect_btw_ray_and_sgmt(q, q2, theta):
"""
Args:
q (Coord): first endpoint of the segment
q2 (Coord): second endpoint of the segment
theta (float): angle of the ray
Returns:
(float): np.inf if the ray does not intersect the segment,
the distance from the origin of the intersection otherwise
"""
assert isinstance(q, Coord) and isinstance(q2, Coord)
s = q2 - q
r = Coord(np.cos(theta), np.sin(theta))
cross = r * s # 2d cross-product
t_num = q * s
u_num = q * r
## the intersection point is roughly at a distance t_num / cross
## from the origin. But some cases must be checked beforehand.
## (1) the segment [PQ2] is aligned with the ray
if np.isclose(cross, 0) and np.isclose(u_num, 0):
return min(q.radius, q2.radius)
## (2) the segment [PQ2] is parallel with the ray
elif np.isclose(cross, 0):
return np.inf
t, u = t_num / cross, u_num / cross
## There is actually an intersection point
if t >= 0 and 0 <= u <= 1:
return t
## (3) No intersection point
return np.inf
For instance find_intersect_btw_ray_and_sgmt(Coord(1, 2), Coord(-1, 2), np.pi / 2) should returns 2.
Note that here for simplicity, I only considered the case where the origin of the rays is at Coord(0, 0). This can be easily extended to the general case by setting t_num = (q - origin) * s and u_num = (q - origin) * r.
Let's vectorize it!
What is very interesting here is that the operations defined in the Coord class also apply to cases where x and y are numpy arrays! Hence applying any defined operation on Coord(np.array([1, 2, 0]), np.array([2, -1, 3])) amounts applying it elementwise to the points (1, 2), (2, -1) and (0, 3). The operations of Coord are therefore already vectorized. The constructor can be modified into:
def __init__(self, x, y):
x, y = np.array(x), np.array(y)
assert x.shape == y.shape
self.x, self.y = x, y
self.shape = x.shape
Now, we would like the function find_intersect_btw_ray_and_sgmt to be able to handle the case where the parameters q and q2contains sequences of endpoints. Before the sanity checks, all the operations are working properly since, as we have mentioned, they are already vectorized. As you mentionned the conditional statements can be "vectorized" using masks. Here is what I propose:
def find_intersect_btw_ray_and_sgmts(q, q2, theta):
assert isinstance(q, Coord) and isinstance(q2, Coord)
assert q.shape == q2.shape
EPS = 1e-14
s = q2 - q
r = Coord(np.cos(theta), np.sin(theta))
cross = r * s
cross_sign = np.sign(cross)
cross = cross * cross_sign
t_num = (q * s) * cross_sign
u_num = (q * r) * cross_sign
radii = np.zeros_like(t_num)
mask = ~np.isclose(cross, 0) & (t_num >= -EPS) & (-EPS <= u_num) & (u_num <= cross + EPS)
radii[~mask] = np.inf # no intersection
radii[mask] = t_num[mask] / cross[mask] # intersection
return radii
Note that cross, t_num and u_num are multiplied by the sign of cross to ensure that the division by cross keeps the sign of the dividends. Hence conditions of the form ((t_num >= 0) & (cross >= 0)) | ((t_num <= 0) & (cross <= 0)) can be replaced by (t_num >= 0).
For simplicity, we omitted the case (1) where the radius and the segment were aligned ((cross == 0) & (u_num == 0)). This could be incorporated by carefully adding a second mask.
For a given value of theta, we are able to determine if the corresponing ray intersects with several segments at once.
## Some useful functions
def polar_to_cartesian(r, theta):
return Coord(r * np.cos(theta), r * np.sin(theta))
def plot_segments(p, q, *args, **kwargs):
plt.plot([p.x, q.x], [p.y, q.y], *args, **kwargs)
def plot_rays(radii, thetas, *args, **kwargs):
endpoints = polar_to_cartesian(radii, thetas)
n = endpoints.shape
origin = Coord(np.zeros(n), np.zeros(n))
plot_segments(origin, endpoints, *args, **kwargs)
## Data generation
M = 5 # size of the canvas
N = 10 # number of segments
K = 16 # number of rays
q = Coord(*np.random.uniform(-M/2, M/2, size=(2, N)))
p = q + Coord(*np.random.uniform(-M/2, M/2, size=(2, N)))
thetas = np.linspace(0, 2 * np.pi, K, endpoint=False)
## For each ray, find the minimal distance of intersection
## with all segments
plt.figure(figsize=(5, 5))
plot_segments(p, q, "royalblue", marker=".")
for theta in thetas:
radii = find_intersect_btw_ray_and_sgmts(p, q, theta)
radius = np.min(radii)
if not np.isinf(radius):
plot_rays(radius, theta, color="orange")
else:
plot_rays(2*M, theta, ':', c='orange')
plt.plot(0, 0, 'kx')
plt.xlim(-M, M)
plt.ylim(-M, M)
And that's not all! Thanks to the broadcasting of python, it is possible to avoid iteration on theta values. For example, recall that np.array([1, 2, 3]) * np.array([[1], [2], [3], [4]]) produces a matrix of size 4 × 3 of the pairwise products. In the same way Coord([[5],[7]], [[5],[1]]) * Coord([2, 4, 6], [-2, 4, 0]) outputs a 2 × 3 matrix containing all the pairwise cross product between vectors (5, 5), (7, 1) and (2, -2), (4, 4), (6, 0).
Finally, the intersections can be determined in the following way:
radii_all = find_intersect_btw_ray_and_sgmts(p, q, np.vstack(thetas))
# p and q have a shape of (N,) and np.vstack(thetas) of (K, 1)
# this radii_all have a shape of (K, N)
# radii_all[k, n] contains the distance from the origin of the intersection
# between k-th ray and n-th segment (or np.inf if there is no intersection point)
radii = np.min(radii_all, axis=1)
# radii[k] contains the distance from the origin of the closest intersection
# between k-th ray and all segments
do_intersect = ~np.isinf(radii)
plot_rays(radii[do_intersect], thetas[do_intersect], color="orange")
plot_rays(2*M, thetas[~do_intersect], ":", color="orange")

Snap a point to the closest part of a line?

I have a work order management system that has Python 2.7.
In the system, it is only possible to use the libraries that are included in the standard Python 2.7 library (it's not possible to import other libraries).
The system has lines and points:
Line Vertex 1: 676561.00, 4860927.00
Line Vertex 2: 676557.00, 4860939.00
Point 100: 676551.00, 4860935.00
Point 200: 676558.00, 4860922.00
I want to snap the points to the nearest part of the line.
Snapping can be defined as:
Moving a point so that it coincides exactly with the closest part of a line.
There appear to be two different mathematical scenarios at play:
A. The closest part of a line is the position along the line where the point is perpendicular to the line.
B. Or, the closest part of the line is simply the closest vertex.
Is it possible to snap a point to the closest part of a line using Python 2.7 (and the standard 2.7 library)?
This is an extremely helpful resource.
import collections
import math
Line = collections.namedtuple('Line', 'x1 y1 x2 y2')
Point = collections.namedtuple('Point', 'x y')
def lineLength(line):
dist = math.sqrt((line.x2 - line.x1)**2 + (line.y2 - line.y1)**2)
return dist
## See http://paulbourke.net/geometry/pointlineplane/
## for helpful formulas
## Inputs
line = Line(0.0, 0.0, 100.0, 0.0)
point = Point(50.0, 1500)
## Calculations
print('Inputs:')
print('Line defined by: ({}, {}) and ({}, {})'.format(line.x1, line.y1, line.x2, line.y2))
print('Point "P": ({}, {})'.format(point.x, point.y))
len = lineLength(line)
if (len == 0):
raise Exception('The points on input line must not be identical')
print('\nResults:')
print('Length of line (calculated): {}'.format(len))
u = ((point.x - line.x1) * (line.x2 - line.x1) + (point.y - line.y1) * (line.y2 - line.y1)) / (
len**2)
# restrict to line boundary
if u > 1:
u = 1
elif u < 0:
u = 0
nearestPointOnLine = Point(line.x1 + u * (line.x2 - line.x1), line.y1 + u * (line.y2 - line.y1))
shortestLine = Line(nearestPointOnLine.x, nearestPointOnLine.y, point.x, point.y)
print('Nearest point "N" on line: ({}, {})'.format(nearestPointOnLine.x, nearestPointOnLine.y))
print('Length from "P" to "N": {}'.format(lineLength(shortestLine)))
You can calculate the line equation and then the distance between each point and the line.
For instance:
import collections
import math
Point = collections.namedtuple('Point', "x, y")
def distance(pt, a, b, c):
# line eq: ax + by + c = 0
return math.fabs(a * pt.x + b * pt.y + c) / math.sqrt(a**2 + b**2)
l1 = Point(676561.00, 4860927.00)
l2 = Point(676557.00, 4860939.00)
# line equation
a = l2.y - l1.y
b = l1.x - l2.x
c = l2.x * l1.y - l2.y * l1.x
assert a * l1.x + b * l1.y + c == 0
assert a * l2.x + b * l2.y + c == 0
p100 = Point(676551.00, 4860935.00)
p200 = Point(676558.00, 4860922.00)
print(distance(p100, a, b, c))
print(distance(p200, a, b, c))
You get:
6.957010852370434
4.427188724235731
Edit1: calculating the orthographic projection
What you want is the coordinates of the orthographic projection of p100 and p200 on the line (l1, l2).
You can calculate that as follow:
import collections
import math
Point = collections.namedtuple('Point', "x, y")
def snap(pt, pt1, pt2):
# type: (Point, Point, Point) -> Point
v = Point(pt2.x - pt1.x, pt2.y - pt1.y)
dv = math.sqrt(v.x ** 2 + v.y ** 2)
bh = ((pt.x - pt1.x) * v.x + (pt.y - pt1.y) * pt2.y) / dv
h = Point(
pt1.x + bh * v.x / dv,
pt1.y + bh * v.y / dv
)
return h
l1 = Point(676561.00, 4860927.00)
l2 = Point(676557.00, 4860939.00)
p100 = Point(676551.00, 4860935.00)
p200 = Point(676558.00, 4860922.00)
s100 = snap(p100, l1, l2)
s200 = snap(p200, l1, l2)
print(s100)
print(p100)
You get:
Point(x=-295627.7999999998, y=7777493.4)
Point(x=676551.0, y=4860935.0)
You can check that the snapped points are on the line:
# line equation
a = l2.y - l1.y
b = l1.x - l2.x
c = l2.x * l1.y - l2.y * l1.x
assert math.fabs(a * s100.x + b * s100.y + c) < 1e-6
assert math.fabs(a * s200.x + b * s200.y + c) < 1e-6
Edit2: snap to the line segment
If you want to snap to a line segment, you need to check if the orthographic projection is inside the line segment or not.
If the orthographic projection is inside the line segment: it is the solution,
If it is near an extremity of the segment, this extremity is the solution.
You can do that as bellow:
def distance_pts(pt1, pt2):
v = Point(pt2.x - pt1.x, pt2.y - pt1.y)
dv = math.sqrt(v.x ** 2 + v.y ** 2)
return dv
def snap(pt, pt1, pt2):
# type: (Point, Point, Point) -> Point
v = Point(pt2.x - pt1.x, pt2.y - pt1.y)
dv = distance_pts(pt1, pt2)
bh = ((pt.x - pt1.x) * v.x + (pt.y - pt1.y) * pt2.y) / dv
h = Point(pt1.x + bh * v.x / dv, pt1.y + bh * v.y / dv)
if 0 <= (pt1.x - h.x) / (pt2.x - h.y) < 1:
# in the line segment
return h
elif distance_pts(h, pt1) < distance_pts(h, pt2):
# near pt1
return pt1
else:
# near pt2
return pt2
The solutions for p100 and p200 are:
Point(x=676557.0, y=4860939.0)
Point(x=676551.0, y=4860935.0)
Another option:
# snap a point to a 2d line
# parameters:
# A,B: the endpoints of the line
# C: the point we want to snap to the line AB
# all parameters must be a tuple/list of float numbers
def snap_to_line(A,B,C):
Ax,Ay = A
Bx,By = B
Cx,Cy = C
# special case: A,B are the same point: just return A
eps = 0.0000001
if abs(Ax-Bx) < eps and abs(Ay-By) < eps: return [Ax,Ay]
# any point X on the line can be represented by the equation
# X = A + t * (B-A)
# so for point C we compute its perpendicular D on the line
# and the parameter t for D
# if t is between 0..1 then D is on the line so the snap point is D
# if t < 0 then the snap point is A
# if t > 1 then the snap point is B
#
# explanation of the formula for distance from point to line:
# http://paulbourke.net/geometry/pointlineplane/
#
dx = Bx-Ax
dy = By-Ay
d2 = dx*dx + dy*dy
t = ((Cx-Ax)*dx + (Cy-Ay)*dy) / d2
if t <= 0: return A
if t >= 1: return B
return [dx*t + Ax, dy*t + Ay]
if __name__=="__main__":
A,B = [676561.00, 4860927.00],[676557.00, 4860939.00]
C = [676551.00, 4860935.00]
print(snap_to_line(A,B,C))
C = [676558.00, 4860922.00]
print(snap_to_line(A,B,C))

Optimizing Numpy Euclidean Distance and Direction Function

I am trying to calculate the euclidean distance and direction from a source coordinate within a numpy array.
Graphic Example
Here is what I was able to come up with, however it is relatively slow for large arrays. Euclidean Distance and Direction based on source coordinates rely heavily on the index of each cell. that is why I am looping each row and column. I have looked into scipy cdist, pdist, and np linalg.
import numpy as np
from math import atan, degrees, sqrt
from timeit import default_timer
def euclidean_from_source(input_array, y_index, x_index):
# copy arrays
distance = np.empty_like(input_array, dtype=float)
direction = np.empty_like(input_array, dtype=int)
# loop each row
for i, row in enumerate(X):
# loop each cell
for c, cell in enumerate(row):
# get b
b = x_index - i
# get a
a = y_index - c
hypotenuse = sqrt(a * a + b * b) * 10
distance[i][c] = hypotenuse
direction[i][c] = get_angle(a, b)
return [distance, direction]
def calibrate_angle(a, b, angle):
if b > 0 and a > 0:
angle+=90
elif b < 0 and a < 0:
angle+=270
elif b > 0 > a:
angle+=270
elif a > 0 > b:
angle+=90
return angle
def get_angle(a, b):
# get angle
if b == 0 and a == 0:
angle = 0
elif b == 0 and a >= 0:
angle = 90
elif b == 0 and a < 0:
angle = 270
elif a == 0 and b >= 0:
angle = 180
elif a == 0 and b < 0:
angle = 360
else:
theta = atan(b / a)
angle = degrees(theta)
return calibrate_angle(a, b, angle)
if __name__ == "__main__":
dimension_1 = 5
dimension_2 = 5
X = np.random.rand(dimension_1, dimension_2)
y_index = int(dimension_1/2)
x_index = int(dimension_2/2)
start = default_timer()
distance, direction = euclidean_from_source(X, y_index, x_index)
print('Total Seconds {{'.format(default_timer() - start))
print(distance)
print(direction)
UPDATE
I was able to use the broadcasting function to do exactly what I needed, and at a fraction of the speed. however I am still figuring out how to calibrate the angle to 0, 360 throughout the matrix (modulus will not work in this scenario).
import numpy as np
from math import atan, degrees, sqrt
from timeit import default_timer
def euclidean_from_source_update(input_array, y_index, x_index):
size = input_array.shape
center = (y_index, x_index)
x = np.arange(size[0])
y = np.arange(size[1])
# use broadcasting to get euclidean distance from source point
distance = np.multiply(np.sqrt((x - center[0]) ** 2 + (y[:, None] - center[1]) ** 2), 10)
# use broadcasting to get euclidean direction from source point
direction = np.rad2deg(np.arctan2((x - center[0]) , (y[:, None] - center[1])))
return [distance, direction]
def euclidean_from_source(input_array, y_index, x_index):
# copy arrays
distance = np.empty_like(input_array, dtype=float)
direction = np.empty_like(input_array, dtype=int)
# loop each row
for i, row in enumerate(X):
# loop each cell
for c, cell in enumerate(row):
# get b
b = x_index - i
# get a
a = y_index - c
hypotenuse = sqrt(a * a + b * b) * 10
distance[i][c] = hypotenuse
direction[i][c] = get_angle(a, b)
return [distance, direction]
def calibrate_angle(a, b, angle):
if b > 0 and a > 0:
angle+=90
elif b < 0 and a < 0:
angle+=270
elif b > 0 > a:
angle+=270
elif a > 0 > b:
angle+=90
return angle
def get_angle(a, b):
# get angle
if b == 0 and a == 0:
angle = 0
elif b == 0 and a >= 0:
angle = 90
elif b == 0 and a < 0:
angle = 270
elif a == 0 and b >= 0:
angle = 180
elif a == 0 and b < 0:
angle = 360
else:
theta = atan(b / a)
angle = degrees(theta)
return calibrate_angle(a, b, angle)
if __name__ == "__main__":
dimension_1 = 5
dimension_2 = 5
X = np.random.rand(dimension_1, dimension_2)
y_index = int(dimension_1/2)
x_index = int(dimension_2/2)
start = default_timer()
distance, direction = euclidean_from_source(X, y_index, x_index)
print('Total Seconds {}'.format(default_timer() - start))
start = default_timer()
distance2, direction2 = euclidean_from_source_update(X, y_index, x_index)
print('Total Seconds {}'.format(default_timer() - start))
print(distance)
print(distance2)
print(direction)
print(direction2)
Update 2
Thanks everyone for the responses, after testing methods, these two methods were the fastest and produced the results I needed. I am still open to any optimizations you guys can think of.
def get_euclidean_direction(input_array, y_index, x_index):
rdist = np.arange(input_array.shape[0]).reshape(-1, 1) - x_index
cdist = np.arange(input_array.shape[1]).reshape(1, -1) - y_index
direction = np.mod(np.degrees(np.arctan2(rdist, cdist)), 270)
direction[y_index:, :x_index]+= -90
direction[y_index:, x_index:]+= 270
direction[y_index][x_index] = 0
return direction
def get_euclidean_distance(input_array, y_index, x_index):
size = input_array.shape
center = (y_index, x_index)
x = np.arange(size[0])
y = np.arange(size[1])
return np.multiply(np.sqrt((x - center[0]) ** 2 + (y[:, None] - center[1]) ** 2), 10)
This operation is extremely easy to vectorize. For one thing, a and b don't need to be computed in 2D at all, since they only depend on one direction in the array. The distance can be computed with np.hypot. Broadcasting will convert the shape into the correct 2D form.
Your angle function is almost exactly equivalent to applying np.degrees to np.arctan2.
It's unclear why you label your rows with xand columns with y instead of the standard way of doing it, but as long as you're consistent, it should be fine.
So here's the vectorized version:
def euclidean_from_source(input_array, c, r):
rdist = np.arange(input_array.shape[0]).reshape(-1, 1) - r
# Broadcasting doesn't require this second reshape
cdist = np.arange(input_array.shape[1]).reshape(1, -1) - c
distance = np.hypot(rdist, cdist) * 10
direction = np.degrees(np.arctan2(rdist, cdist))
return distance, direction
I will leave it as an exercise for the reader to determine if any additional processing is necessary to fine-tune the angle, and if so, to implement it in a vectorized manner.
Might be easier to just pass the corrdinate you want to measure as an array or tuple. Also, while it might take a bit more memory, I think using np.indices might be a bit faster for the calculation (as it allows np.einsum to do its magic).
def euclidean_from_source(input_array, coord):
grid = np.indices(input_array.shape)
grid -= np.asarray(coord)[:, None, None]
distance = np.einsum('ijk, ijk -> jk', grid, grid) ** .5
direction = np.degrees(np.arctan2(grid[0], grid[1]))
return distance, direction
This method is also a bit more extensible to n-d (although obviously the angle calculations would be a bit trickier

Quiver plot arrows in Matplotlib are ridiculously too large

I was working on creating a python script that could model electric field lines, but the quiver plot comes out with arrows that are way too large. I've tried changing the units and the scale, but the documentation on matplotlib makes no sense too me... This seems to only be a major issue when there is only one charge in the system, but the arrows are still slightly oversized with any number of charges. The arrows tend to be oversized in all situations, but it is most evident with only one particle.
import matplotlib.pyplot as plt
import numpy as np
import sympy as sym
import astropy as astro
k = 9 * 10 ** 9
def get_inputs():
inputs_loop = False
while inputs_loop is False:
""""
get inputs
"""
inputs_loop = True
particles_loop = False
while particles_loop is False:
try:
particles_loop = True
"""
get n particles with n charges.
"""
num_particles = int(raw_input('How many particles are in the system? '))
parts = []
for i in range(num_particles):
parts.append([float(raw_input("What is the charge of particle %s in Coulombs? " % (str(i + 1)))),
[float(raw_input("What is the x position of particle %s? " % (str(i + 1)))),
float(raw_input('What is the y position of particle %s? ' % (str(i + 1))))]])
except ValueError:
print 'Could not convert input to proper data type. Please try again.'
particles_loop = False
return parts
def vec_addition(vectors):
x_sum = 0
y_sum = 0
for b in range(len(vectors)):
x_sum += vectors[b][0]
y_sum += vectors[b][1]
return [x_sum,y_sum]
def electric_field(particle, point):
if particle[0] > 0:
"""
Electric field exitation is outwards
If the x position of the particle is > the point, then a different calculation must be made than in not.
"""
field_vector_x = k * (
particle[0] / np.sqrt((particle[1][0] - point[0]) ** 2 + (particle[1][1] - point[1]) ** 2) ** 2) * \
(np.cos(np.arctan2((point[1] - particle[1][1]), (point[0] - particle[1][0]))))
field_vector_y = k * (
particle[0] / np.sqrt((particle[1][0] - point[0]) ** 2 + (particle[1][1] - point[1]) ** 2) ** 2) * \
(np.sin(np.arctan2((point[1] - particle[1][1]), (point[0] - particle[1][0]))))
"""
Defining the direction of the components
"""
if point[1] < particle[1][1] and field_vector_y > 0:
print field_vector_y
field_vector_y *= -1
elif point[1] > particle[1][1] and field_vector_y < 0:
print field_vector_y
field_vector_y *= -1
else:
pass
if point[0] < particle[1][0] and field_vector_x > 0:
print field_vector_x
field_vector_x *= -1
elif point[0] > particle[1][0] and field_vector_x < 0:
print field_vector_x
field_vector_x *= -1
else:
pass
"""
If the charge is negative
"""
elif particle[0] < 0:
field_vector_x = k * (
particle[0] / np.sqrt((particle[1][0] - point[0]) ** 2 + (particle[1][1] - point[1]) ** 2) ** 2) * (
np.cos(np.arctan2((point[1] - particle[1][1]), (point[0] - particle[1][0]))))
field_vector_y = k * (
particle[0] / np.sqrt((particle[1][0] - point[0]) ** 2 + (particle[1][1] - point[1]) ** 2) ** 2) * (
np.sin(np.arctan2((point[1] - particle[1][1]), (point[0] - particle[1][0]))))
"""
Defining the direction of the components
"""
if point[1] > particle[1][1] and field_vector_y > 0:
print field_vector_y
field_vector_y *= -1
elif point[1] < particle[1][1] and field_vector_y < 0:
print field_vector_y
field_vector_y *= -1
else:
pass
if point[0] > particle[1][0] and field_vector_x > 0:
print field_vector_x
field_vector_x *= -1
elif point[0] < particle[1][0] and field_vector_x < 0:
print field_vector_x
field_vector_x *= -1
else:
pass
return [field_vector_x, field_vector_y]
def main(particles):
"""
Graphs the electrical field lines.
:param particles:
:return:
"""
"""
plot particle positions
"""
particle_x = 0
particle_y = 0
for i in range(len(particles)):
if particles[i][0]<0:
particle_x = particles[i][1][0]
particle_y = particles[i][1][1]
plt.plot(particle_x,particle_y,'r+',linewidth=1.5)
else:
particle_x = particles[i][1][0]
particle_y = particles[i][1][1]
plt.plot(particle_x,particle_y,'r_',linewidth=1.5)
"""
Plotting out the quiver plot.
"""
parts_x = [particles[i][1][0] for i in range(len(particles))]
graph_x_min = min(parts_x)
graph_x_max = max(parts_x)
x,y = np.meshgrid(np.arange(graph_x_min-(graph_x_max-graph_x_min),graph_x_max+(graph_x_max-graph_x_min)),
np.arange(graph_x_min-(graph_x_max-graph_x_min),graph_x_max+(graph_x_max-graph_x_min)))
if len(particles)<2:
for x_pos in range(int(particles[0][1][0]-10),int(particles[0][1][0]+10)):
for y_pos in range(int(particles[0][1][0]-10),int(particles[0][1][0]+10)):
vecs = []
for particle_n in particles:
vecs.append(electric_field(particle_n, [x_pos, y_pos]))
final_vector = vec_addition(vecs)
distance = np.sqrt((final_vector[0] - x_pos) ** 2 + (final_vector[1] - y_pos) ** 2)
plt.quiver(x_pos, y_pos, final_vector[0], final_vector[1], distance, angles='xy', scale_units='xy',
scale=1, width=0.05)
plt.axis([particles[0][1][0]-10,particles[0][1][0]+10,
particles[0][1][0] - 10, particles[0][1][0] + 10])
else:
for x_pos in range(int(graph_x_min-(graph_x_max-graph_x_min)),int(graph_x_max+(graph_x_max-graph_x_min))):
for y_pos in range(int(graph_x_min-(graph_x_max-graph_x_min)),int(graph_x_max+(graph_x_max-graph_x_min))):
vecs = []
for particle_n in particles:
vecs.append(electric_field(particle_n,[x_pos,y_pos]))
final_vector = vec_addition(vecs)
distance = np.sqrt((final_vector[0]-x_pos)**2+(final_vector[1]-y_pos)**2)
plt.quiver(x_pos,y_pos,final_vector[0],final_vector[1],distance,angles='xy',units='xy')
plt.axis([graph_x_min-(graph_x_max-graph_x_min),graph_x_max+(graph_x_max-graph_x_min),graph_x_min-(graph_x_max-graph_x_min),graph_x_max+(graph_x_max-graph_x_min)])
plt.grid()
plt.show()
g = get_inputs()
main(g)}
You may set the scale such that it roughly corresponds to the u and v vectors.
plt.quiver(x_pos, y_pos, final_vector[0], final_vector[1], scale=1e9, units="xy")
This would result in something like this:
If I interprete it correctly, you want to draw the field vectors for point charges. Looking around at how other people have done that, one finds e.g. this blog entry by Christian Hill. He uses a streamplot instead of a quiver but we might take the code for calculating the field and replace the plot.
In any case, we do not want and do not need 100 different quiver plots, as in the code from the question, but only one single quiver plot that draws the entire field. We will of course run into a problem if we want to have the field vector's length denote the field strength, as the magnitude goes with the distance from the particles by the power of 3. A solution might be to scale the field logarithmically before plotting, such that the arrow lengths are still somehow visible, even at some distance from the particles. The quiver plot's scale parameter then can be used to adapt the lengths of the arrows such that they somehow fit to other plot parameters.
""" Original code by Christian Hill
http://scipython.com/blog/visualizing-a-vector-field-with-matplotlib/
Changes made to display the field as a quiver plot instead of streamlines
"""
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Circle
def E(q, r0, x, y):
"""Return the electric field vector E=(Ex,Ey) due to charge q at r0."""
den = ((x-r0[0])**2 + (y-r0[1])**2)**1.5
return q * (x - r0[0]) / den, q * (y - r0[1]) / den
# Grid of x, y points
nx, ny = 32, 32
x = np.linspace(-2, 2, nx)
y = np.linspace(-2, 2, ny)
X, Y = np.meshgrid(x, y)
charges = [[5.,[-1,0]],[-5.,[+1,0]]]
# Electric field vector, E=(Ex, Ey), as separate components
Ex, Ey = np.zeros((ny, nx)), np.zeros((ny, nx))
for charge in charges:
ex, ey = E(*charge, x=X, y=Y)
Ex += ex
Ey += ey
fig = plt.figure()
ax = fig.add_subplot(111)
f = lambda x:np.sign(x)*np.log10(1+np.abs(x))
ax.quiver(x, y, f(Ex), f(Ey), scale=33)
# Add filled circles for the charges themselves
charge_colors = {True: 'red', False: 'blue'}
for q, pos in charges:
ax.add_artist(Circle(pos, 0.05, color=charge_colors[q>0]))
ax.set_xlabel('$x$')
ax.set_ylabel('$y$')
ax.set_xlim(-2,2)
ax.set_ylim(-2,2)
ax.set_aspect('equal')
plt.show()
(Note that the field here is not normalized in any way, which should no matter for visualization at all.)
A different option is to look at e.g. this code which also draws field lines from point charges.

Drawing directions fields

Is there a way to draw direction fields in python?
My attempt is to modify http://www.compdigitec.com/labs/files/slopefields.py giving
#!/usr/bin/python
import math
from subprocess import CalledProcessError, call, check_call
def dy_dx(x, y):
try:
# declare your dy/dx here:
return x**2-x-2
except ZeroDivisionError:
return 1000.0
# Adjust window parameters
XMIN = -5.0
XMAX = 5.0
YMIN = -10.0
YMAX = 10.0
XSCL = 0.5
YSCL = 0.5
DISTANCE = 0.1
def main():
fileobj = open("data.txt", "w")
for x1 in xrange(int(XMIN / XSCL), int(XMAX / XSCL)):
for y1 in xrange(int(YMIN / YSCL), int(YMAX / YSCL)):
x= float(x1 * XSCL)
y= float(y1 * YSCL)
slope = dy_dx(x,y)
dx = math.sqrt( DISTANCE/( 1+math.pow(slope,2) ) )
dy = slope*dx
fileobj.write(str(x) + " " + str(y) + " " + str(dx) + " " + str(dy) + "\n")
fileobj.close()
try:
check_call(["gnuplot","-e","set terminal png size 800,600 enhanced font \"Arial,12\"; set xrange [" + str(XMIN) + ":" + str(XMAX) + "]; set yrange [" + str(YMIN) + ":" + str(YMAX) + "]; set output 'output.png'; plot 'data.txt' using 1:2:3:4 with vectors"])
except (CalledProcessError, OSError):
print "Error: gnuplot not found on system!"
exit(1)
print "Saved image to output.png"
call(["xdg-open","output.png"])
return 0
if __name__ == '__main__':
main()
However the best image I get from this is.
How can I get an output that looks more like the first image? Also, how can I add the three solid lines?
You can use this matplotlib code as a base. Modify it for your needs.
I have updated the code to show same length arrows. The important option is to set the angles option of the quiver function, so that the arrows are correctly printed from (x,y) to (x+u,y+v) (instead of the default, which just takes into account of (u,v) when computing the angles).
It is also possible to change the axis form "boxes" to "arrows". Let me know if you need that change and I could add it.
import matplotlib.pyplot as plt
from scipy.integrate import odeint
import numpy as np
fig = plt.figure()
def vf(x, t):
dx = np.zeros(2)
dx[0] = 1.0
dx[1] = x[0] ** 2 - x[0] - 2.0
return dx
# Solution curves
t0 = 0.0
tEnd = 10.0
# Vector field
X, Y = np.meshgrid(np.linspace(-5, 5, 20), np.linspace(-10, 10, 20))
U = 1.0
V = X ** 2 - X - 2
# Normalize arrows
N = np.sqrt(U ** 2 + V ** 2)
U = U / N
V = V / N
plt.quiver(X, Y, U, V, angles="xy")
t = np.linspace(t0, tEnd, 100)
for y0 in np.linspace(-5.0, 0.0, 10):
y_initial = [y0, -10.0]
y = odeint(vf, y_initial, t)
plt.plot(y[:, 0], y[:, 1], "-")
plt.xlim([-5, 5])
plt.ylim([-10, 10])
plt.xlabel(r"$x$")
plt.ylabel(r"$y$")
I had a lot of fun making one of these as a hobby project using pygame. I plotted the slope at each pixel, using shades of blue for positive and shades of red for negative. Black is for undefined. This is dy/dx = log(sin(x/y)+cos(y/x)):
You can zoom in & out - here is zoomed in on the middle upper part here:
and also click on a point to graph the line going through that point:
It's just 440 lines of code, so here is the .zip of all the files. I guess I'll excerpt relevant bits here.
The equation itself is input as a valid Python expression in a string, e.g. "log(sin(x/y)+cos(y/x))". This is then compiled. This function here graphs the color field, where self.func.eval() gives the dy/dx at the given point. The code is a bit complicated here because I made it render in stages - first 32x32 blocks, then 16x16, etc. - to make it snappier for the user.
def graphcolorfield(self, sqsizes=[32,16,8,4,2,1]):
su = ScreenUpdater(50)
lastskip = self.xscreensize
quitit = False
for squaresize in sqsizes:
xsquaresize = squaresize
ysquaresize = squaresize
if squaresize == 1:
self.screen.lock()
y = 0
while y <= self.yscreensize:
x = 0
skiprow = y%lastskip == 0
while x <= self.xscreensize:
if skiprow and x%lastskip==0:
x += squaresize
continue
color = (255,255,255)
try:
m = self.func.eval(*self.ct.untranscoord(x, y))
if m >= 0:
if m < 1:
c = 255 * m
color = (0, 0, c)
else:
#c = 255 - 255 * (1.0/m)
#color = (c, c, 255)
c = 255 - 255 * (1.0/m)
color = (c/2.0, c/2.0, 255)
else:
pm = -m
if pm < 1:
c = 255 * pm
color = (c, 0, 0)
else:
c = 255 - 255 * (1.0/pm)
color = (255, c/2.0, c/2.0)
except:
color = (0, 0, 0)
if squaresize > 1:
self.screen.fill(color, (x, y, squaresize, squaresize))
else:
self.screen.set_at((x, y), color)
if su.update():
quitit = True
break
x += xsquaresize
if quitit:
break
y += ysquaresize
if squaresize == 1:
self.screen.unlock()
lastskip = squaresize
if quitit:
break
This is the code which graphs a line through a point:
def _grapheqhelp(self, sx, sy, stepsize, numsteps, color):
x = sx
y = sy
i = 0
pygame.draw.line(self.screen, color, (x, y), (x, y), 2)
while i < numsteps:
lastx = x
lasty = y
try:
m = self.func.eval(x, y)
except:
return
x += stepsize
y = y + m * stepsize
screenx1, screeny1 = self.ct.transcoord(lastx, lasty)
screenx2, screeny2 = self.ct.transcoord(x, y)
#print "(%f, %f)-(%f, %f)" % (screenx1, screeny1, screenx2, screeny2)
try:
pygame.draw.line(self.screen, color,
(screenx1, screeny1),
(screenx2, screeny2), 2)
except:
return
i += 1
stx, sty = self.ct.transcoord(sx, sy)
pygame.draw.circle(self.screen, color, (int(stx), int(sty)), 3, 0)
And it runs backwards & forwards starting from that point:
def graphequation(self, sx, sy, stepsize=.01, color=(255, 255, 127)):
"""Graph the differential equation, given the starting point sx and sy, for length
length using stepsize stepsize."""
numstepsf = (self.xrange[1] - sx) / stepsize
numstepsb = (sx - self.xrange[0]) / stepsize
self._grapheqhelp(sx, sy, stepsize, numstepsf, color)
self._grapheqhelp(sx, sy, -stepsize, numstepsb, color)
I never got around to drawing actual lines because the pixel approach looked too cool.
Try changing your values for the parameters to this:
XSCL = .2
YSCL = .2
These parameters determine how many points are sampled on the axes.
As per your comment, you'll need to also plot the functions for which the derivation dy_dx(x, y) applies.
Currently, you're only calculating and plotting the slope lines as calculated by your function dy_dx(x,y). You'll need to find (in this case 3) functions to plot in addition to the slope.
Start by defining a function:
def f1_x(x):
return x**3-x**2-2x;
and then, in your loop, you'll have to also write the desired values for the functions into the fileobj file.

Categories