Finding Equations for Common Tangents Between Two Ellipses, A General Solution

conic sectionsgeometrylinear algebratangent line

I'm interested in finding the four common tangents between two ellipses. While I've found some fascinating approaches using dual conics to identify their intersection points (link 1, link 2), my primary objective is to obtain the tangent equations in the original Cartesian coordinate system.

The methods I've come across seem to work within the realm of projective geometry, which is beyond my current mathematical expertise. Any help in simplifying this to Cartesian coordinates would be invaluable.

My Current Understanding

Let's say we have two ellipses defined by their canonical matrices E1 and E2.

  1. Dual Conics: The common tangents can be found at the intersection points of the dual conics C1 = E1^-1 and C2 = E2^-1.

  2. Intersection Points: To find these, we consider the pencil of conics C1 - t * C2 (where t is real) and solve for t in det(C1 - t * C2) = 0. Since this is a cubic equation, it yields up to three roots t0, t1, and t2, which correspond to up to three degenerate conics—usually intersecting lines. The equation for these degenerate conics is C1 - t_i * C2 = 0.

  3. Coordinates of Intersection Points: As these points lie on the conics, for each t_i we should have:

    (C1 - t_i * C2) * X_i = 0
    

    Subsequently:

    ((C2^-1 * C1) - t_i * I) * X_i = 0
    

Based on an answer by @Futurologist here, this is an eigenvalue equation, with t_i as the eigenvalue and X_i as the eigenvector. Therefore, X_i should give me the coordinates of the intersection points, which should also provide the coefficients for the tangents in Cartesian coordinates.

Have I understood this correctly, or is there something I'm missing?

Example
Let's have a concrete example with the 2 ellipsis:

E1: 36 * x^2 + 9 * y^2 - 324 = 0

and

E2: 4 * x^2 + y^2 - 56 * x - 6 * y + 201 = 0

Considering the method I described above, please can you explain how you get the four tangents final equations?

Best Answer

Update: complex numbers

This implementation with complex numbers seems to work for now

import math
import cmath
import numpy as np

'''
Algorithm for finding the coefficients of 
the linear equations of the four tangent 
lines of a pair of non-intersecting ellipses
'''

# given a pair of conics, as a pair of symmetric matrices, 
# calculate the vector k = (k[0], k[1], k[2]) of values for each of which 
# the conic c1 - k[i]*c2 from the pencil of conics c1 - t*c2 
# is a degenerate conic (the anti-symmetric product of a pair of linear forms) 
# and also find the matrix U 
# of the projective transformation that simplifies the geometry of 
# the pair of conics, and the geometry of the pencil c1 - t*c2 in general, 
# as well as the geometry of the three degenerate conics in particular

def get_transformation(c1, c2):
    '''
    c1 and c2 are 3 by 3 symmetric matrices of the two conics
    '''
    c21 = np.linalg.inv(c2).dot(c1)
    k, U = np.linalg.eig(c21)
    return k, U

def find_common_points(c1, c2):
    k, U = get_transformation(c1, c2)
    L1 = (U.T).dot((c1 - k[0]*c2).dot(U))
    L2 = (U.T).dot((c1 - k[1]*c2).dot(U))
    x_0 = cmath.sqrt(-(L2[2,2] / L2[0,0]))
    y_0 = cmath.sqrt(-(L1[2,2] / L1[1,1]))
    sol = np.array([[ x_0,  y_0, 1], 
                    [-x_0, -y_0, 1],
                    [-x_0,  y_0, 1],
                    [ x_0, -y_0, 1]])
    sol = sol.dot(U.T)
    return sol

# combination of the algorithms form above with the 
# transformation of the problem of common tangents of the two ellipses
# to its dual - the intersection points of the two dual ellipses 
# The intersection points of the dual ellipses
# are the common tangents of the two original ellipses

def find_common_tangents(c1, c2):
    dc1 = np.linalg.inv(c1)
    dc2 = np.linalg.inv(c2)
    return find_common_points(dc1, dc2)

Previous version: real version, non-intersecting ellipses

If an algorithm is what you are seeking, here is one, based on my previous posts you cited:

import math
import numpy as np

'''
Algorithm for finding the coefficients of 
the linear equations of the four tangent 
lines of a pair of non-intersecting ellipses
'''

# given a pair of conics, as a pair of symmetric matrices, 
# calculate the vector k = (k[0], k[1], k[2]) of values for each of which 
# the conic c1 - k[i]*c2 from the pencil of conics c1 - t*c2 
# is a degenerate conic (the anti-symmetric product of a pair of linear forms) 
# and also find the matrix U 
# of the projective transformation that simplifies the geometry of 
# the pair of conics, and the geometry of the pencil c1 - t*c2 in general, 
# as well as the geometry of the three degenerate conics in particular
   
def get_transformation(c1, c2):
    '''
    c1 and c2 are 3 by 3 symmetric matrices of the two conics
    '''
    c21 = np.linalg.inv(c2).dot(c1)
    k, U = np.linalg.eig(c21)
    return k, U


# find the common points, i.e. points of intersection, 
# of a pair of non-intersecting ellipses
# represented by a pair of symmetric matrices

def find_common_points(c1, c2):
    '''
    c1 and c2 are 3 by 3 symmetric matrices of the two conics
    '''
    k, U = get_transformation(c1, c2)
    L1 = (U.T).dot((c1 - k[0]*c2).dot(U))
    L2 = (U.T).dot((c1 - k[1]*c2).dot(U))
    sol = np.ones((4,3), dtype=float)
    for i in range(2):
        for j in range(2):
            sol[i+2*j,0:2] = np.array([math.sqrt(abs(L2[2,2] / L2[0,0]))*(-1)**i, math.sqrt(abs(L1[2,2] / L1[1,1]))*(-1)**j])
    sol = sol.dot(U.T)
    return sol

# combination of the algorithms form above with the 
# transformation of the problem of common tangents of the two ellipses
# to its dual - the intersection points of the two dual ellipses 
# The intersection points of the dual ellipses
# are the common tangents of the two original ellipses

def find_common_tangents(c1, c2):
    dc1 = np.linalg.inv(c1)
    dc2 = np.linalg.inv(c2)
    return find_common_points(dc1, dc2)

I tested it with one example, and it worked, but have not run more tests. Here is a test script:

'''
Begin generation of test conics
'''

def cos_sin(angle_deg):
    return math.cos(angle_deg*math.pi/180), math.sin(angle_deg*math.pi/180)

def rotation(cs_sn):
    return np.array([[cs_sn[0], -cs_sn[1]], 
                     [cs_sn[1],  cs_sn[0]]])

def isom(angle, translation):
    '''
    #isometry from conic-aligned coordinate system (conic attached)
    #to global coordinate system (world system) 
    isometry from global coordinate system (world system) 
    to conic-aligned coordinate system (conic attached) 
    '''
    cos_, sin_ = cos_sin(-angle)
    tr = - rotation((cos_, sin_)).dot(translation)
    return np.array([[ cos_, -sin_, tr[0]], 
                     [ sin_,  cos_, tr[1]], 
                     [    0,     0,    1 ]])

def conic(major, minor, angle, center):
    D = np.array([[minor**2,        0,                 0],
                  [       0, major**2,                 0], 
                  [       0,        0, -(minor*major)**2]])
    U = isom(angle, center)
    return (U.T).dot(D.dot(U)) 

def conic_coeff(c):
    return np.array( (c[0,0], 2*c[0,1], c[1,1], 2*c[0,2], 2*c[1,2], c[2,2]) )

'''End generation of test conics'''


# BEGIN test:

rnd=5 # rounding to 5 digits after the decimal point. Choose as you like.

a = 2
b = 1
cntr = np.array([0,0])
w = 45

C1 = conic(a, b, w, cntr)
Eq1 = conic_coeff(C1).round(rnd)

a = 3
b = 1
cntr = np.array([7,0])
w = 120

C2 = conic(a, b, w, cntr)
Eq2 = conic_coeff(C2).round(rnd)

common_tangents = find_common_tangents(C1, C2)

print()
print('equation of the conic 1:')
print(f'{Eq1[0]}x**2 + {Eq1[1]}xy + {Eq1[2]}y**2 + {Eq1[3]}x + {Eq1[4]}y + {Eq1[5]} = 0')
print()
print('equation of conic 2:')
print(f'{Eq2[0]}x**2 + {Eq2[1]}xy + {Eq2[2]}y**2 + {Eq2[3]}x + {Eq2[4]}y + {Eq2[5]} = 0')
print()

print('The coefficients of the linear equations of the four tangent lines to the conics 1 and 2 are: ')
print(common_tangents.round(rnd))

After running this particular test, I got as output:

equation of conic 1:
2.5x**2 + -3.0xy + 2.5y**2 + 0.0x + 0.0y + -4.0 = 0

equation of conic 2:
7.0x**2 + 6.9282xy + 3.0y**2 + -98.0x + -48.49742y + 334.0 = 0

the coefficients of the linear equations of the four tangent lines to the conics 1 and 2 are: 
[[ 0.52106  0.85509 -1.96045]
 [ 0.07325  0.63866  1.08326]
 [-0.274    1.24082 -1.73691]
 [-0.72181  1.02439  1.3068 ]]

Then I went to Desmos, because I find it convenient, and plotted the output, just to check visually:

enter image description here

Related Question