Introduction
This problem was recently brought to my attention and requested to make a contribution. I will answer this question in 4 different ways. I will also numerically compare the solutions via Python code, and I will provide some 2D and 3D graphics to help illustrate the concepts.
The paper by Tovchigrechko and Vakser (TV) (mentioned and linked in another entry) describes the solution geometrically and is similar to Christian Blatter’s solution, and it is similar to the spherical trigonometry approach shown in this entry. One note about the TV paper: their cap radii are measured along the surface of the sphere unlike the OP’s request to be straight-line distances constrained to the plane of the cap’s base.
Solution
Here are the steps to solve the intersection problem (all angles are in radians throughout this entry; for more context about the givens see the OP and Figure 1 (r is the radius, a1 and a2 are straight line distances, and $\theta$ is an angular separation)). We will assume each cap has the hemisphere as its limiting case; also, as noted in other entries, we are assuming an intersection occurs where neither cap is completely contained within the other:
Figure 1: 3D graph showing the givens.
$\text{Given a1, a2, r, and }\theta:$
$\text{let a}=\sin ^{-1}\left(\text{a1}/r\right),$
$\text{b}=\sin ^{-1}\left(\text{a2}/r\right),$
$\text{c}=\theta,$
$s=\frac{1}{2} (a+b+c),$
$k=\sqrt{\frac{\sin (s-a) \sin (s-b) \sin (s-c)}{\sin (s) }},$
$A=2 \tan ^{-1}\left(\frac{k}{\sin (s-a)}\right),$
$B=2 \tan ^{-1}\left(\frac{k}{\sin (s-b)}\right),$
$C=2 \tan ^{-1}\left(\frac{k}{\sin (s-c)}\right),$
$\Delta _1=r^2 (A+B+C-\pi ),$
$\Delta _2=r^2 (A+B+C-\pi ),$
$\mathcal{A}_{\text{s1}}=2 B r^2 (1-\cos (a) ),$
$\mathcal{A}_{\text{s2}}=2 A r^2 (1-\cos (b) )$
$$ \bbox[5px,border:2px solid red]
{
\mathcal{A}_{\cap }=\mathcal{A}_{\text{s1}}+\mathcal{A}_{\text{s2}}-\left(\Delta _1+\Delta _2\right)
}
$$
That’s the answer (this, and different spherical angle formulas, can also be used to derive variants of the formula including the one found in Tovchigrechko and Vakser). The rest of this entry just provides more context and explanation.
More Info
The following is equivalent to the above steps, and is the result found in the TV paper except the $r^2$ part (a1, a2, and $\theta$ are spherical distances, i.e., distances along the curved surface on the unit sphere; to make a1 and a2 above work here, convert them like so: t=arcsin(a1/r) and u=arcsin(a2/r)):
$2 r^2 (\pi$
$-\cos ^{-1}(\csc (\text{t}) \csc (\text{u}) \cos (\theta )-\cot (\text{t}) \cot (\text{u}))$
$-\cos ^{-1}(\csc (\text{t}) \cos (\text{u}) \csc (\theta )-\cot (\text{t}) \cot (\theta ))\cos (\text{t}) $
$-\cos ^{-1}(\cos (\text{t}) \csc (\text{u}) \csc (\theta )-\cot (\text{u}) \cot (\theta ))\cos (\text{u}) )$
Figure 2: Two examples of an intersection.
Figure 2a and 2b show an intersection of 2 spherical caps. Plane I and Plane II are the planar bases of their respective caps; the planes are used to help highlight the intersection, and they'll be used later to convert the problem into 2D. We'll use Figure 2a for the following discussion.
Figure 3: Spherical sectors.
Figure 3 shows the 2 separate spherical sectors, i.e., a red area and a cyan area. The summation of the red and cyan areas contains the intersection of the spherical caps, but notice the 2 spherical triangles that get included twice when adding the red area and the cyan area. The area of these 2 spherical triangles needs to be removed from the combined area of the 2 sectors. (In this context a and b have a radius component that needs to be divided out. In the intersection formula given above the radius component is removed by the time the sector area is computed, which is why the formulas in Figure 3 appear slightly different compared to $\mathcal{A}_{\text{s1}}$ and $\mathcal{A}_{\text{s2}}$ in the intersection formula.)
Figure 4: Spherical triangle close-up.
Figure 4 shows what’s involved to calculate the area of the 2 spherical triangles whose combined area must be removed from the combined area of the 2 spherical sectors.
The angles of the spherical triangle were calculated using the half-angle formulas for tangents (equations 27 and 34-38):
/SphericalTrigonometry
And the spherical triangle area formula appears here:
/SphericalTriangle
Both topics are at Wolfram's Mathworld, but I can't make these links because my account is limited to only 8 links at the moment.
Numerical Verification (or Additional Solutions)
Cartesian Coordinates
Next, we’ll look at comparing the formula with numerical integration over a region. Imagine rotating the objects in Figure 2 such that you are looking down the line where the planes intersect. The planes would then appear like lines. Here is a 2D version of what that looks like (the red lines suggest the continuation of the planes beyond the circle):
Figure 5: 2D perspective of an intersection.
In Figure 5, a1 and a2 are radii of their respective cap shown here for context (and the radius, r, is shown to avoid confusion with $\mathcal{R}$ of the region). The blue area is the 2D projected region where the caps intersect. We will rotate (i.e., change $\theta$, a given) one cap across the span of the other cap to test the area of several different intersections as the series of images in Figure 6a show. In 6b a few perspectives of the 2 intersecting caps are shown in relation to one of the regions. One reason for doing this sweep is to consider extreme points where we might encounter numerical instability.
Figure 6: Integration in Cartesian coordinates.
Figure 6c shows the concepts involved in computing the integral of the intersecting caps in Cartesian coordinates. The double integral, with respect to the function $r / \sqrt{r^2-x^2-y^2}$, is improper and likely for that reason it presented some problems for scipy. I was able to get it to work by changing the variables in both the double integral and single integral versions, but in the code I’ve chosen the single integral version as it makes the code a little easier to read. This corresponds with the set of integrals below the dashed horizontal line in Figure 6c, which applies to the inner integral the common technique of making the substitution $y=\lvert b\rvert \sin (\theta )$. The $\sin ^{-1}\left(\frac{\lvert \sqrt{r^2-x^2} \rvert}{\lvert \sqrt{r^2-x^2} \rvert}\right)$ can be reduced to $\pi /2$.
Monte-Carlo Integration
Next, a simple approximation of the integration will be setup for the purpose of being a guard rail for catching obvious errors that might be present. In particular, a Monte-Carlo integration will be used. Here we simply remove the integrand from the double integrals in Figure 6c and integrate the scalar constant of 1 over the same regions as shown in Figure 6d. If we trust the region definitions (i.e., if they’re simple) and if the functions are well behaved, then this presents a robust approach to compute the area; for these reasons it’s the approach I used to compute the region’s area needed in the Monte-Carlo function in the Python code example. We don’t need to multiply by 2 since this is the 2D region area, not the surface area on the sphere above and below the region. The value of this region area is multiplied by the average of the $r / \sqrt{r^2-x^2-y^2}$ evaluated at each point in a random set of points within the region, which yields the Monte-Carlo approximation of the surface above the region (multiply by 2 to include the area below the region).
Integration in Spherical Coordinates
The advantage in spherical coordinates is we’ll avoid an improper integral for the most part, but a disadvantage is the area formula will appear more complex. Here’s a parametric form of a spherical cap we can use to calculate its area (it was designed for this discussion and is not intended to be flexible enough for general use (the domain of $\phi$ will depend on the cap, but for this problem $\theta$ always runs from $-\pi \text{ to } \pi$)):
$x=r \cos (\phi )$
$y=r \sin (\phi ) \cos \left(\theta f(\phi )\right)$
$z=r \sin (\phi ) \sin \left(\theta f(\phi )\right)$
$\text{where} f(\phi )=\frac{1}{\pi}\tan ^{-1}\left(\frac{\sqrt{-(b\;+\;m\;r \cos (\phi ))^2\;+\;r^2 \left(-\cos ^2(\phi )\right)\;+\;r^2}}{b\;+\;m\;r \cos (\phi )}\right)$
$\text{and}-\pi \leq \theta \leq \pi$
Figure 7: Integration in spherical coordinates.
Figure 7a is a visualization showing how the point at (1,0,0) is transformed to a/the red arc along the spherical cap in accordance with the given parametric form. Multiplication of the matrices in Figure 7a yield the above parametric form. The $f(\phi )$ part; though cluttered, is just computing the intersection at the x-value of $\phi$ along the line of a spherical cap’s base (viewing it edgewise), and then projecting that intersection upwards and downwards onto the sphere, which then yields points along the red arc in Figure 7a with respect to $\theta$ as it sweeps across from $-\pi \text{ to } \pi$ between those 2 projections, and finally sweeping it across $\phi$ yields the cap’s surface. The images in Figure 7b were computed via the parametric form above and serve as a visual verification that the parametric form is correct.
The area of a parameterized surface is equal to the magnitude of infinitesimal tangent plane normals integrated over the region, i.e.:
$\underset{\mathcal{R}}{\int }\;\lvert\lvert \mathbb{T}_u\times \mathbb{T}_v\rvert\rvert \;du\;dv$
and in our context of having x, y and z given in terms of two parameters is equivalent to (this is how it is done in the Python code in the appendix):
$\underset{\mathcal{R}}{\int }\;\sqrt{\left[\frac{\partial (x,y)}{\partial (u,v)}\right]^2+\left[\frac{\partial (y,z)}{\partial (u,v)}\right]^2+\left[\frac{\partial (x,z)}{\partial (u,v)}\right]^2}\;du\;dv=\underset{\mathcal{R}}{\int }\;\sqrt{\left\lvert
\begin{array}{cc}
\frac{\partial x}{\partial u} & \frac{\partial x}{\partial v} \\
\frac{\partial y}{\partial u} & \frac{\partial y}{\partial v} \\
\end{array}
\right\rvert ^2+\left\lvert
\begin{array}{cc}
\frac{\partial y}{\partial u} & \frac{\partial y}{\partial v} \\
\frac{\partial z}{\partial u} & \frac{\partial z}{\partial v} \\
\end{array}
\right\rvert ^2+\left\lvert
\begin{array}{cc}
\frac{\partial x}{\partial u} & \frac{\partial x}{\partial v} \\
\frac{\partial z}{\partial u} & \frac{\partial z}{\partial v} \\
\end{array}
\right\rvert ^2}\;du\;dv$
(see section 7.4, Vector Calculus, 3ed. Jerrold E. Marsden and Anthony J. Tromba.)
(The use of u and v here instead of $\phi$ and $\theta$ is just to avoid implicitly suggesting that the formula has something to do with angles.)
See Figure 7c for more details about the regions of integration for our parametric form.
Results
Figure 8: Test results.
Figure 8 shows the results of running calculations of the first formula presented (Main), integration in Cartesian coordinates (Cart.Coords), integration in spherical coordinates (Sph.Coords), integration via Monte-Carlo (M.Carlo), and via the formula found in Tovchigrechko and Vakser (TV) with the added radius aspect, and these are for various angles of separation for radius equal to 1/3, 1, and 3, along with a1 and a2 values. The nan results in the Monte-Carlo are likely just caused by not having enough sample points fall within the tiny region, but the Monte-Carlo was just there as a sanity check, so not a big deal if we’re missing some values.
Appendix
Code
import numpy as np
from numpy import cos, arcsin, arccos, sin, tan, arctan, arctan2
from numpy import sqrt, pi, array, dot
from scipy import integrate, optimize
from math import isinf
# Note: I don't follow a naming convention here. Also, no attempt at
# being efficient was made. The purpose is to use alternative approaches
# to find discrepancies with the main function, i.e., AreaOfCapsIntersection.
# Here is our main process for computing the area of
# intersecting spherical caps. a1 and a2 are the straight line
# distance of the radius of each cap's respective base. r is the sphere's
# radius. theta is the angular separation between the caps (in radians).
def AreaOfCapsIntersection(a1, a2, r, theta):
a = arcsin(a1 / r)
b = arcsin(a2 / r)
c = theta
s = (a + b + c) / 2
k = sqrt((sin(s - a) * sin(s - b) * sin(s - c) / sin(s)))
A = 2 * arctan(k / sin(s - a))
B = 2 * arctan(k / sin(s - b))
C = 2 * arctan(k / sin(s - c))
T1 = r**2 * (A + B + C - pi)
T2 = T1
S1 = 2 * B * r**2 * (1 - cos(a))
S2 = 2 * A * r**2 * (1 - cos(b))
return S1 + S2 - (T1 + T2)
# This is from the Tovchigrechko and Vakser paper mentioned in another entry
# and modified to account for a radius other than 1. a1 and a2 have been
# adjusted to the distance on the sphere's surface by the calling code.
# Theta is also expected to be the angular separation of the 2 caps.
def TovchigrechkoAndVakser(a1, a2, r, theta):
def csc(t):
return 1 / sin(t)
def cot(t):
return 1 / tan(t)
return r**2 * 2 * (pi
- arccos(cos(theta) * csc(a1) * csc(a2) - cot(a1) * cot(a2))
- arccos(cos(a2) * csc(theta) * csc(a1) - cot(theta) * cot(a1)) * cos(a1)
- arccos(cos(a1) * csc(theta) * csc(a2) - cot(theta) * cot(a2)) * cos(a2))
# This will return the distance from the origin to the base of the
# spherical cap given its baseRadius and the sphere's radius.
def FindCapDistance(baseRadius, r):
return abs(optimize.brentq(lambda h:
sqrt(abs(baseRadius)**2 + abs(h)**2) - r, 0, 1000))
# Rotate a point about the origin
def Rotate(theta, x, y):
c, s = cos(theta), sin(theta)
R = [[c, -s],
[s, c]]
pt = array([x, y]).reshape(2, 1)
return dot(R, pt).flatten()
# Find the angle that rotates [xFrom, yFrom] onto [xTo, yTo].
def FindRotation(xFrom, yFrom, xTo, yTo):
u0, u1, v0, v1 = xFrom, yFrom, xTo, yTo
return arctan2(u0 * v1 - u1 * v0, u0 * v0 + u1 * v1)
# Rescales x along the interval [a,b] to the interval [c,d].
def Rescale(x, a, b, c, d):
return (-(b * c) + a * d + (c - d) * x) / (a - b)
# Given the vectors a=fromPt and b=toPt from the origin find a point
# along the vector a+t*(b-a) at parameter t where t will be rescaled
# as [fromPt[0], toPt[0]] to [0,1].
def PointAlongLine(fromPt, toPt, t):
a, b = array(fromPt), array(toPt)
return a + Rescale(t, a[0], b[0], 0, 1) * (b - a)
# Finds the point where two lines intersect. Each parameter is a 2D array.
def FindIntersectionOfLines(l1From, l1To, l2From, l2To):
def f(x):
pt1 = PointAlongLine(l1From, l1To, x[0])
pt2 = PointAlongLine(l2From, l2To, x[0])
# The 2-norm is the same thing as Euclidean distance in our context:
return np.linalg.norm(pt1 - pt2)
# Because we're rescaling t in a+t(b-a) to coincide with the bounds
# both functions are evaluated at the same x, so we just need 1 dimension.
x0 = array([0])
# The function is well behaved, keep it simple, nelder-mead will be fine.
r = optimize.minimize(f, x0, method='nelder-mead', options={'xatol': 1e-8})
return PointAlongLine(l1From, l1To, r.x[0])
# Integrate in Cartesian coordiantes given the radius, r, the
# Cartesian 2D region start point (p11, p12), the intersecting
# point (p21, p22), and the end point (p31, p32). (See article for graphics.)
def IntegrateInCartesianCoordinates(r, p11, p12, p21, p22, p31, p32):
r2 = r**2
def commonIntegrand(a, b, x):
d = sqrt(r2 - x**2)
return r * (arcsin(b / d) - arcsin(a / d))
def f(x):
return PointAlongLine([p11, p12], [p21, p22], x)[1]
def g(x):
return PointAlongLine([p21, p22], [p31, p32], x)[1]
areaType = 1 if p31 > p21 else (2 if p31 == p21 else 3)
if areaType == 1:
i1 = integrate.quad(lambda x:
commonIntegrand(f(x), sqrt(r2 - x**2), x), p11, p21)[0]
i2 = integrate.quad(lambda x:
commonIntegrand(g(x), sqrt(r2 - x**2), x), p21, p31)[0]
return 2 * (i1 + i2)
elif areaType == 2:
return 2 * integrate.quad(lambda x:
commonIntegrand(f(x), sqrt(r2 - x**2), x), p11, p21)[0]
elif areaType == 3:
def h(x):
# p31 may look out of place; it's the projection onto the horz line
return PointAlongLine([p31, p22], [p21, p22], x)[1]
def i(x):
return PointAlongLine([p31, p32], [p21, p22], x)[1]
i1 = integrate.quad(lambda x:
commonIntegrand(f(x), sqrt(r2 - x**2), x), p11, p31)[0]
i2 = integrate.quad(lambda x:
commonIntegrand(h(x), i(x), x), p31, p21)[0]
return 2 * (i1 + i2)
else:
raise Exception("Area type not recognized.")
# Monte-Carlo integration in Cartesian coordiantes given the radius, r, the
# Cartesian 2D region start point (p11, p12), the intersecting
# point (p21, p22), and the end point (p31, p32). (See article for graphics.)
def MonteCarloIntegration(r, p11, p12, p21, p22, p31, p32):
sampleSize = 100000
r2 = r**2
def cross(p1, p2):
return p1[0] * p2[1] - p2[0] * p1[1]
def leftOf(p1, p2, p3): # is point p3 left of line [p1, p2]
a1, a2, a3 = map(array, [p1, p2, p3])
return cross(a3 - a2, a2 - a1) <= 0 # keep on the line too i.e. == 0
def f(x):
return PointAlongLine([p11, p12], [p21, p22], x)[1]
def g(x):
return PointAlongLine([p21, p22], [p31, p32], x)[1]
def h(x):
# p31 may look out of place; it's the projection onto the horz. line.
return PointAlongLine([p31, p22], [p21, p22], x)[1]
def i(x):
return PointAlongLine([p31, p32], [p21, p22], x)[1]
def interval1(x):
return [f(x), sqrt(r2 - x**2)]
def interval2(x):
return [g(x), sqrt(r2 - x**2)]
def interval3(x):
return [h(x), i(x)]
# Given line1 [p1, p2] and line2 [p2, p3] get random points within the
# disk of radius r and to the left of l1 and l2. Note, p2 is the end
# point of l1 and the start point of l2.
def mcIntegrand(p1, p2, p3):
u1 = np.random.uniform(-r, r, sampleSize)
u2 = np.random.uniform(-r, r, sampleSize)
pts = []
for i in zip(u1, u2):
x2 = i[0]**2
y2 = i[1]**2
if x2 + y2 < r2 \
and leftOf(p1, p2, [i[0], i[1]]) \
and leftOf(p2, p3, [i[0], i[1]]):
pts.append(r / sqrt(r2 - i[0]**2 - i[1]**2))
return np.mean(list(pts))
areaType = 1 if p31 > p21 else (2 if p31 == p21 else 3)
if areaType == 1:
i1 = integrate.nquad(lambda y, x: 1, [interval1, [p11, p21]])[0]
i2 = integrate.nquad(lambda y, x: 1, [interval2, [p21, p31]])[0]
area = i1 + i2
return 2 * area * mcIntegrand([p11, p12], [p21, p22], [p31, p32])
elif areaType == 2:
area = integrate.nquad(lambda y, x: 1, [interval1, [p11, p21]])[0]
return 2 * area * mcIntegrand([p11, p12], [p21, p22], [p31, p32])
elif areaType == 3:
i1 = integrate.nquad(lambda y, x: 1, [interval1, [p11, p31]])[0]
i2 = integrate.nquad(lambda y, x: 1, [interval3, [p31, p21]])[0]
area = i1 + i2
return 2 * area * mcIntegrand([p11, p12], [p21, p22], [p31, p32])
else:
raise Exception("Area type not recognized.")
# Integration in spherical coordiantes given the radius, r, the
# Cartesian 2D region start point (p11, p12), the intersecting
# point (p21, p22), and the end point (p31, p32). (See article for graphics.)
# Too slow; should be rewritten.
def SphericalCoordinateIntegration(r, p11, p12, p21, p22, p31, p32):
# Just in case these names used by sympy may collide with the outer
# code, they'll be scoped here as a precaution.
from sympy import sin, cos, Matrix, atan, pi, sqrt
from sympy.abc import rho, phi, theta, m, b
from sympy import lambdify
y = b + m * rho * cos(phi)
fphi = atan(sqrt(-(y)**2 + rho**2 * (-(cos(phi)**2)) + rho**2) / y) / pi
xyz = Matrix([rho * cos(phi),
rho * sin(phi) * cos(theta * fphi),
rho * sin(phi) * sin(theta * fphi)])
uv = Matrix([phi, theta])
mat = xyz.jacobian(uv)
f = lambdify("phi, theta, rho, m, b",
Matrix([[mat[0,0], mat[0,1]], [mat[1,0], mat[1,1]]]).det()**2)
g = lambdify("phi, theta, rho, m, b",
Matrix([[mat[1,0], mat[1,1]], [mat[2,0], mat[2,1]]]).det()**2)
h = lambdify("phi, theta, rho, m, b",
Matrix([[mat[0,0], mat[0,1]], [mat[2,0], mat[2,1]]]).det()**2)
t1 = FindRotation(1, 0, p11, p12) # pt is on the circle
t2 = FindRotation(1, 0, p21, np.sqrt(r**2 - p21**2)) # project onto circle
t3 = FindRotation(1, 0, p31, p32) # pt is on the circle
try: slope1 = (p22 - p12) / (p21 - p11)
except ZeroDivisionError: slope1 = float('+inf')
b1 = PointAlongLine([p11, p12], [p21, p22], 0)[1]
try: slope2 = (p32 - p22) / (p31 - p21)
except ZeroDivisionError: slope2 = float('+inf')
b2 = PointAlongLine([p21, p22], [p31, p32], 0)[1]
# The integrals are negated b/c the outer integral is done clockwise.
i1 = -integrate.nquad(lambda p,t: np.sqrt(f(p, t, r, slope1, b1)
+ g(p, t, r, slope1, b1) + h(p, t, r, slope1, b1))
, [[t1, t2], [-pi, pi]])[0]
if isinf(slope2):
return i1
else:
i2 = -integrate.nquad(lambda p,t: np.sqrt(f(p, t, r, slope2, b2)
+ g(p, t, r, slope2, b2) + h(p, t, r, slope2, b2))
, [[t2, t3], [-pi, pi]])[0]
return i1 + i2
################################################
## Check the functions
################################################
def WithinTol(value):
assert abs(value) <= 1e-8
WithinTol(abs(FindCapDistance(1, 1)))
WithinTol(abs(FindCapDistance(0.7453559924999299, 1) - 2/3))
assert Rescale(15, 10, 20, 0, 1) == 0.5
assert PointAlongLine([1,2], [11,3], 6)[1] == 2.5
WithinTol(abs(FindRotation(1, 0, -0.5, 0.86602540378443) - 2 * pi / 3))
WithinTol(abs(Rotate(2 * pi / 3, 1, 0)[0] - -0.5))
WithinTol(abs(Rotate(2 * pi / 3, 1, 0)[1] - 0.8660254037844386))
_r = FindIntersectionOfLines([5, 1], [1, 10], [1, 2], [10, 5])
WithinTol(abs(_r[0] - 4.09677419))
WithinTol(abs(_r[1] - 3.03225807))
print("Tests passed.")
################################################
## Driver
################################################
print("MonteCarloIntegration is the slowest function here.")
print("Disable it or set its sample size smaller to speed things up.")
print(".....")
print("SphericalCoordinateIntegration is also slow, but would need to")
print("be rewritten to account for its slowness.")
print(".....")
print("The calls to MonteCarloIntegration and SphericalCoordinateIntegration")
print("have been commented out of the Driver.")
print(".....")
# Each row holds a value respectively for these elements:
# [radius, a1, a2].
rows = [
[1/3, 0.235702, 0.321975],
[1, 0.707107, 0.965926],
[3, 2.12132, 2.89778]]
for row in rows:
[r, a1, a2] = row
p11 = [-a1, FindCapDistance(a1, r)]
p12 = [a1, p11[1]]
p21 = [-a2, FindCapDistance(a2, r)]
p22 = [a2, p21[1]]
# Calculate the angle step size to sweep across:
# fromAngle = FindRotation(1, 0, p12[0], p12[1]) \
# - FindRotation(1, 0, p22[0], p22[1]) + 0.000174533
# toAngle = FindRotation(1, 0, p11[0], p11[1]) \
# - FindRotation(1, 0, p22[0], p22[1])- 0.000174533
# stepSize = (toAngle - fromAngle) / 4
# atAngle = fromAngle
# I am inserting a pi/2 angle to the above angles to try a vertical line.
angles = [0.5237767175432327,0.916388451764179,1.3090001859851252,
1.5707963267948966,1.7016119202060715,2.094223654427018]
# Generate the report for the current arguments:
#while atAngle <= toAngle:
for atAngle in angles:
print([r, a1, a2, atAngle])
rp21 = Rotate(atAngle, p21[0], p21[1])
rp22 = Rotate(atAngle, p22[0], p22[1])
intersection = FindIntersectionOfLines(p11, p12, rp21, rp22)
x11, x12 = p11[0], p11[1]
x21, x22 = intersection[0], intersection[1]
x31, x32 = rp22[0], rp22[1]
print(AreaOfCapsIntersection(a1, a2, r, atAngle))
print(IntegrateInCartesianCoordinates(r, x11, x12, x21, x22, x31, x32))
#print(SphericalCoordinateIntegration(r, x11, x12, x21, x22, x31, x32))
#print(MonteCarloIntegration(r, x11, x12, x21, x22, x31, x32))
print(TovchigrechkoAndVakser(arcsin(a1 / r), arcsin(a2 / r), r, atAngle))
#atAngle += stepSize