The code below handles the case of a hyperbola. It largely adapts the code from here
import numpy as np
import matplotlib.pyplot as plt
def equation_to_matrix(eq):
'''
eq[0]*x**2 + eq[1]*x*y + eq[2]*y**2 + eq[3]*x + eq[4]*y + eq[5] = 0
'''
return np.array([[2*eq[0], eq[1], eq[3]],
[ eq[1], 2*eq[2], eq[4]],
[ eq[3], eq[4], 2*eq[5]]]) / 2
def hyp_params_from_general(coeffs):
# get the matrix of the quadratic equation
Aq = equation_to_matrix(coeffs)
# get the matrix of the quadratic form A33
A33 = Aq[:2, :2]
# determinant of A33
detA33 = np.linalg.det(A33)
if detA33 > 0:
raise ValueError('coeffs do not represent a hyperbola: det A33 must be negative!')
# get the center
x0 = -np.linalg.det(np.array([Aq[:2, 2], Aq[:2, 1]]).T) / detA33
y0 = -np.linalg.det(np.array([Aq[:2, 0], Aq[:2, 2]]).T) / detA33
# The semi-major and semi-minor axis lengths (these are not sorted).
# get discriminant of the conic section
delta = np.linalg.det(Aq)
# get the eigenvalues
k1, k2 = np.linalg.eigvals(A33)
k1isk2 = np.isclose(k1/k2, -1)
ap = np.sqrt(abs(delta/k1/detA33))
bp = np.sqrt(abs(delta/k2/detA33))
# Eccentricity.
fac = np.sqrt((Aq[0, 0] - Aq[1, 1])**2 + Aq[0, 1]**2)
if delta < 0:
nu = 1
else:
nu = -1
e = np.sqrt(2*fac/(nu*(Aq[0, 0] - Aq[1, 1]) + fac))
# slope of the asymptotes
if Aq[0, 0] == Aq[1, 1] and k1isk2:
m1 = 0.
m2 = np.nan
else:
m1 = Aq[0, 0]/(-Aq[0, 1] - np.sqrt(-detA33))
m2 = Aq[0, 0]/(-Aq[0, 1] + np.sqrt(-detA33))
# Sort the semi-major and semi-minor axis lengths but keep track of
# the original relative magnitudes of width and height.
width_gt_height = True
if ap < bp and not k1isk2:
width_gt_height = False
ap, bp = bp, ap
# The angle of anticlockwise rotation of the major-axis from x-axis.
if Aq[0, 1] == 0:
phi = 0 if Aq[0, 0] < Aq[1, 1] else np.pi/2
elif Aq[0, 0] == Aq[1, 1]:
phi = np.pi/4 # would divide by zero and arctan(inf) -> pi/4
if m1 > 0 and m2 > 0:
width_gt_height = True
else:# Aq[0, 0] > Aq[1, 1]:
phi = np.arctan(2*Aq[0, 1]/(Aq[0, 0] - Aq[1, 1])) / 2
if not width_gt_height:
# Ensure that phi is the angle to rotate to the semi-major axis.
phi += np.pi/2
phi = phi % np.pi
return x0, y0, ap, bp, phi, e, m1, m2, width_gt_height
def get_hyperbola_pts(params, npts=100, tmin=-1, tmax=1):
x0, y0, ap, bp, phi, m1, m2 = params
# A grid of the parametric variable, t.
t = np.linspace(tmin, tmax, npts)
# points
x = x0 + ap * np.cosh(t) * np.cos(phi) - bp * np.sinh(t) * np.sin(phi)
y = y0 + ap * np.cosh(t) * np.sin(phi) + bp * np.sinh(t) * np.cos(phi)
# asymptotes
ya1 = y0 + m1*(x - x0)
ya2 = y0 + m2*(x - x0)
return x, y, ya1, ya2
if __name__ == '__main__':
coeffs = [1., 6., -2., 3., 0., 0.]
x0, y0, ap, bp, phi, e, m1, m2, width_gt_height = hyp_params_from_general(coeffs)
print('x0, y0, ap, bp, phi, e, m1, m2, width_gt_height = ', x0, y0, ap, bp, phi, e, m1, m2)
x_, y_, ya1, ya2 = get_hyperbola_pts((x0, y0, ap, bp, phi, m1, m2), npts=250, tmin=-2, tmax=3)
fig, ax = plt.subplots(figsize=(16, 9))
ax.plot(x_, y_, marker='.', linewidth=0.5, c='r')
ax.plot(x_, ya1, marker='.', linewidth=0.2, c='b')
ax.plot(x_, ya2, marker='.', linewidth=0.2, c='b')
ax.grid(True, linestyle='--')