This post shows how to use shapely library to solve complex geometry problems easily.
Statement
Lets say we want to find the intersection of an elliptic curve:
      ![Rendered by QuickLaTeX.com \[y^2 = x^3 - x = P_2\]](https://landercy.be/wp-content/ql-cache/quicklatex.com-41127c958a7d3a63bc333e1f41e98ff0_l3.png)
And a circle:
      ![Rendered by QuickLaTeX.com \[\left(x - \frac{1}{2}\right)^2 + y^2 = 1\]](https://landercy.be/wp-content/ql-cache/quicklatex.com-3ee3589c46758ffeb71eac233c700020_l3.png)
Analytical solution
This problem can be solved analytically by finding the roots of the characteristic polynomial:
      ![Rendered by QuickLaTeX.com \[P_1(x) = x^3 + x^2 - 2x - \frac{3}{4} = 0\]](https://landercy.be/wp-content/ql-cache/quicklatex.com-8914a85b19a649129ac3ecd7fccc2059_l3.png)
Which have three real roots that can be calculated by Cardano formulas but only two makes polynomial  positive hence are real solutions for:
 positive hence are real solutions for:
      ![Rendered by QuickLaTeX.com \[y = \pm\sqrt{P_2} = \pm\sqrt{x^3 - x}\]](https://landercy.be/wp-content/ql-cache/quicklatex.com-3ff2d079757c393fbd9e10ed93f314f1_l3.png)
We expect four real solutions to this problem. This is for algebra, now lets see how we can numerically solve this problem without this knowledge.
Numerical solution w/ Shapely
Shapely is a planar geometry library that exposes the same objects and set of functionalities than PostGIS. Its main target audience is probably GIS applications but it is enough generalist to tract classical analytical geometry as well.
We first import required dependencies:
import numpy as np
import matplotlib.pyplot as plt
from shapely import Point, LineString, MultiLineStringThen we create this small function that convert contour curves into MulitLineString in order to numerically represent implicit function:
def get_curve(model, xmin=-1., xmax=+1., ymin=-1., ymax=+1., level=0., resolution=200, **kwargs):
    xlin = np.linspace(xmin, xmax, resolution)
    ylin = np.linspace(ymin, ymax, resolution)
    X, Y = np.meshgrid(xlin, ylin)
    Z = model(X, Y, **kwargs)
    contour = plt.contour(X, Y, Z, [level])
    curve = MultiLineString([
        LineString(path.vertices) for path in contour.collections[0].get_paths()
    ])
    return curveThis is the key point, our ability to represent implicit curve (even with multiple branches) with Shapely.
Then we create our two implicit equations as zero-level curve:
def elliptic(x, y, a=-1., b=0.):
    return x ** 3 + a * x + b - y ** 2
def circle(x, y, x0=0., y0=0., r=1.):
    return (x - x0) ** 2 + (y - y0) ** 2 - r ** 2And compute them for a given domain of analysis:
c1 = get_curve(elliptic, xmin=-2., xmax=+2., ymin=-2., ymax=+2.)
c2 = get_curve(circle, xmin=-2., xmax=+2., ymin=-2., ymax=+2., x0=+0.5)Now we can unleash the full power of Shapely by finding the intersection of two curves:
solutions = c1.intersection(c2)Which is a MultiPoint object that can be converted to ndarray as follow:
points = np.array([(point.x, point.y) for point in solutions.geoms])
# array([[-0.33730234,  0.54658686],
#        [-0.33730234, -0.54658686],
#        [ 1.19616848, -0.71782881],
#        [ 1.19616848,  0.71782881]])That is we have found the four solutions for this problem by intersecting contour curves of zero-level for both implicit equations.
Graphically it renders as follow:

Numerical confirmation
We numerically solve the characteristic polynomial  with
 with numpy:
xsol = np.roots([1, 1, -2, -3/4])
# array([-1.85887056,  1.19617217, -0.33730161])  Which are potential solutions for  and we assess the
 and we assess the  coordinate as well:
 coordinate as well:
ysol = np.array([np.sqrt(x ** 3 - x)  for x in xsol])
# array([       nan, 0.71787485, 0.54674126])We can see that only two roots of  are real solution for this problem. Hence we have four solutions has we must consider both
 are real solution for this problem. Hence we have four solutions has we must consider both  and
 and  .
.
Graphically it renders as follow:

The precision of those solutions is better but it requires to do algebra by hands where Shapely solved it for use without any knowledge of it.
Alternatively we can also express the system as follow:
def system(x, a, b, x0, y0, r):
    return np.array([
        elliptic(*x, a, b),
        circle(*x, x0, y0, r)
    ])And solve its roots:
p0 = (-1, 0, 0.5, 0, 1)
sol1 = optimize.fsolve(system, x0=[1,1], args=p0)    # array([1.19617217, 0.71787485])
sol2 = optimize.fsolve(system, x0=[1,-1], args=p0)   # array([ 1.19617217, -0.71787485])
sol3 = optimize.fsolve(system, x0=[-1,1], args=p0)   # array([-0.33730161,  0.54674126])
sol4 = optimize.fsolve(system, x0=[-1,-1], args=p0)  # array([-0.33730161, -0.54674126])We can plot the distance field:
xlin = np.linspace(-2, 2, 200)
ylin = np.linspace(-2, 2, 200)
X, Y = np.meshgrid(xlin, ylin)
U, V = system([X, Y], *p0)
Z = np.sqrt(U**2 + V**2)
We can confirm than solution of the system are minima of the distance function.
