This page contains all the theory used to develop this packaged. It can be resumed as a mix of set theory, analitic and parametric geometry on the plane \(\mathbb{R}^2\).
Hereafter we describe the main objects used in this study (Empty, Whole, Point, Curve and Shape), and how they interact with each other.
Point
Point is a 0-dimensional object defined by only a pair of real values \((x, \ y)\).
It can also be understood as a mathematical set of a single element, meaning \(\{(x, \ y)\}\)
Let \(p_0 = \left(x_0, \ y_0\right)\) and \(p_1 = \left(x_1, \ y_1\right)\) be two points. The operations between then are:
Inner product :
Cross product :
Norm L2 of a point :
Curve
Curves are one-dimensional objects, that contains a infinite number of connected points.
A curve can be defined as implicit or parametrized by one variable.
Examples:
Implicit:
Circle
\[C = \left\{\left(x, \ y\right) \in \mathbb{R}^2 : x^2 + y^2 = 1\right\}\]Straight line
\[C = \left\{\left(x, \ y\right) \in \mathbb{R}^2 : 2\cdot x + 5 \cdot y = 10\right\}\]Hyperbola
\[C = \left\{\left(x, \ \dfrac{1}{x}\right) \in \mathbb{R}^2 : x \in \mathbb{R}^{+}\right\}\]Parametrized:
Circle
\[C = \left\{\left(\cos t, \ \sin t\right) \in \mathbb{R}^2 : t \in \left[0, \ 2\pi\right] \subset \mathbb{R}\right\}\]Straight line
\[C = \left\{\left(2+t, \ 4+t\right) \in \mathbb{R}^2 : t \in \mathbb{R}\right\}\]Hyperbola
\[C = \left\{\left(\exp -t, \ \exp t\right) \in \mathbb{R}^2 : t \in \mathbb{R}\right\}\]
For this package * Segment is a \(C^{1}\) parametrized curve \(p(t)\) defined on a interval \(\left[0, \ 1\right]\)
PiecewiseCurve is a parametrized curve that is a sequence of segments.
The curve contains \(n\) segments, that are described by using knots \(\left[t_0, \ t_1, \ \cdots, \ t_n\right]\).
JordanCurve is also a piecewise curve, but it’s continuous, closed and does not intersect itself.
Jordan curve
The jordan curve used in this package:
Is a Closed Curve that doesn’t intersect itself.
Is oriented, either counter-clockwise (positive) or clockwise (negative)
Divides the plane in two regions: Interior and exterior
Is either bounded, or can go to infinity only once.
Can be parametrized by piecewise analitic curves
Examples:
Counter-clockwise circle:
Straight line:
Right hand of an Hyperbola
Note
Although the jordan curve can go to the infinity once, the current implementation doesn’t allow it yet.
Shape
Shape is bi-dimensional object that can be obtained from union and intersection of the internal regions of some Jordan Curves.
For this package, the shapes are classified in three types:
SimpleShape: Defined by only one JordanCurve. It’s the interior area if the jordan is counter-clockwise, otherwise it’s the exterior area
ConnectedShape: It’s the intersection of some SimpleShape
DisjointShape: It’s the union of SimpleShape and ConnectedShape
Simple Shape
If the oriented jordan curve is counter-clockwise, the shape is positive. If it’s clockwise, then we say the shape is negative.
An integral is made to compute the absolute value of the area, and the sign is accordingly with the jordan curve orientation.
Connected Shape
Is similar to a simple shape but has holes.
It can be described as the intersection of some Simple shapes
From construction, max of only one of \(S_i\) is positive. The order used is:
Simple shape with positive area comes first
Then order the rest in increasing order
Disjoint Shape
It’s the union of some disjoint Simple and Connected shapes.
It can be described as the union
From construction, max of only one of \(C_i\) is negative The order used is:
Connected/Simple shape with negative area comes first
Then order the rest in decreasing order
Integral
One of the main uses of shapepy is to compute integrals. It can assume two forms:
Line integrals : When the integration occurs over a curve, one-dimensional integral
Shape integrals : Bidimensional
The computation of the integral can change depending on the function and on the curve/shape. Here we show how we compute
Curve integrals
Three types of line integrals were given.
Shape integrals
The shapes are classified in Simple, Connected and Disjoint.
If \(S\) is disjoint, then it’s the union of subshapes \(S_i\), it’s transformed
If \(S\) is connected, then it’s the intersection of simple shapes \(S_i\), it’s transformed
Meaning, the integral over Connected or Disjoint are transformed into integrals over Simple shapes.
The strategy to integrate over a simple shape is transform the integral over the area, into a integral over the jordan curve (its boundary) by using Green’s theorem
Without loss of generality, take \(\alpha \in \mathbb{R}\) a constant, and set
If \(f(x, \ y)\) is polynomial then
Hence
In special, take \(\alpha = (a+1)/(a+b+2)\) and the integral is transformed
Since every parametric curve is divided in \(n\) intervals, it’s written
This integral is easier computed by using One-dimensional integration.
Polygonal
In special, if \(S\) is a polygon, the integrals can be simplified even more. The curve can be divided into \(n\) segments that connects two vertices \(V_k\) and \(V_{k+1}\).
The right integral can be expanded and then use the integral of beta function
With
def integrate(vertices: np.ndarray, a: int, b: int) -> float:
vertices = np.array(vertices)
if vertices.ndim != 2 or vertices.shape[1] != 2:
raise ValueError(f"Invalid vertices! {vertices.shape}")
matrix = np.zeros((a + 1, b + 1), dtype="int64")
for i in range(a + 1):
for j in range(b + 1):
matrix[i, j] = sp.binomial(i + j, i) * sp.binomial(a + b - i - j, b - j)
shiverts = np.roll(vertices, shift=-1, axis=0)
cross = vertices[:, 0] * shiverts[:, 1] - vertices[:, 1] * shiverts[:, 0]
xvand0 = np.vander(vertices[:, 0], a + 1)
xvand1 = np.vander(shiverts[:, 0], a + 1, True)
yvand0 = np.vander(vertices[:, 1], b + 1)
yvand1 = np.vander(shiverts[:, 1], b + 1, True)
soma = np.einsum("k,ki,ki,ij,kj,kj", cross, xvand0, xvand1, matrix, yvand0, yvand1)
denom = (a + b + 2) * (a + b + 1) * sp.binomial(a + b, a)
return soma / denom
Polynomial
If \(S\) is a polygonomial by parts, then for an interval \(\left[t_k, \ t_{k+1}\right]\)
Contains
Deciding if a set \(A\) is (or not) a subset of \(B\) is not a trivial. This section describes the algorithms to decide it.
Basically either \(A\) and \(B\) can assume the forms of Empty, Point, Curve, Shape, Whole
That means, \(5 \times 5 = 25\) possibilities. The table here after reduces the quantity of verifications to 6, which are represented by the empty spaces
Empty |
Point |
Curve |
Shape |
Whole |
|
|---|---|---|---|---|---|
Empty |
T |
T |
T |
T |
T |
Point |
F |
T |
|||
Curve |
F |
F |
T |
||
Shape |
F |
F |
F |
T |
|
Whole |
F |
F |
F |
F |
T |
The next sections verifies these 6 missing verifications.
Point in Point
Point is a 0-dimensional object, and contains only one element : the point itself. Therefore, a point contains another point if, and only if the points are equal.
Point in Curve
To verify if a curve contains a point \(q\), we compute the projection of this point in the curve.
The projection is computed by finding \(t^{\star}\) such minimizes the distance square:
It’s a positive convex function and therefore there is at least one minimum of this function. Its minimum can occurs at the nodes \(t_k\) or when the derivative is zero:
This equation has at least one solution, but it may have infinite (take a circle as example).
Once the solutions are found, one can compute the distance between the point and the projected point. If this projected point is equal to the point itself, then the point is on the curve.
Point in Shape
The Lebesgue Density function is used to determine if the shape contains the point.
Basically this function tells if a point is inside the shape, or outside, or at the boundary:
If \((x, \ y)\) is at the interior, then \(w(x, \ y) = 1\)
If \((x, \ y)\) is at the exterior, then \(w(x, \ y) = 0\)
If \((x, \ y)\) is at the boundary,:math:0 < w(x, y) < 1
Hence, the verification happens as:
- If shape is simple:
If \(w = 0\), then shape doesn’t contain the point
If \(w > 0\) and shape is closed, then
If \(w = 1\), then shape contains the point
If \(0 < w < 1\), then
Note
The possibility of using the ray-casting algorithm was considered, but it’s Arguments against it are : depending on the direction of the ray, the result can vary. If it touches a vertex, etc.
Also, having a mesure of how acute an angle is very useful.
Curve in Curve
Check if a curve is inside another curve can be done by parts
- Check if the vertices of A’s curve are inside the B’s curve.
That uses Point in Curve
Check if the basis functions are the same.
Curve in Shape
Checking if a curve is inside a shape is made by parts.
If shape is disjoint: Check if the curve is inside any subshape
If shape is connected: Check if the curve is inside all subshapes
- If shape is simple:
- Checks if all vertices are inside the shape.
The vertices are the curve evaluated at knots. This uses the Point in Shape.
- Find the intersection between the curve and the shape’s curve.
If they don’t intersect, the curve is inside the shape. If they do intersect, continue
- Find the parameters where the two curves intersect.
Compute the midpoints. For each midpoint, check if the midpoint is inside the shape.
Shape in Shape
There are three shape classifications: Simple, Connected and Disjoint. Checking A in B have 9 possibilities, which is reduced:
- If \(A\) is disjoint, then
- \[\bigcup_i A_i \subset B \Leftrightarrow \text{all}_{i}\left(A_i \subset B\right)\]
- If \(A\) is simple or connected, and \(B\) is disjoint, then
- \[A \subset \bigcup_{i} B_i \Leftrightarrow \text{any}_{i}\left(A \subset B_i\right)\]
- If \(A\) is simple or connected, and \(B\) is connected, then
- \[A \subset \bigcap_{i} B_i \Leftrightarrow \text{all}_{i}\left(A \subset B_i\right)\]
- If \(A\) is connected, and \(B\) is simple, then
- \[\bigcap_i A_i \subset B \Leftrightarrow \text{all}_i\left(\text{jordan}\left(A_i\right) in B\right) \ \text{and}\]
For Simple in Simple, follows as bellow.
Simple in simple
This part describes how to compare cases when \(A\) and \(B\) are simple shapes. The following statements must be satisfied to \(A \subset B\).
A unbounded shape is not is not contained in a bounded shape
Translated as: If A.area < 0 and B.area > 0, then A not in B
The boundary of \(A\) must be inside of \(B\)
Translated as: If the A.jordan is not inside B, then A not in B
The area from \(A\) must not be greater than the area of \(B\)
Translated as: If A.area > B.area, then A not in B
This consider the cases such, if A.area > 0 and B.area > 0, then it’s
Table simple shapes
Case 1 |
Case 2 |
Case 3 |
Case 4 |
Case 5 |
|---|---|---|---|---|
Case |
\(A\) |
\(B\) |
\(A\) in \(B\) |
\(J_A\) in \(B\) |
\(J_B\) in \(A\) |
\(a_A ? a_B\) |
|---|---|---|---|---|---|---|
1 |
\(+\) |
\(+\) |
F |
F |
F |
? |
\(-\) |
T |
T |
> |
|||
\(-\) |
\(+\) |
F |
F |
T |
< |
|
\(-\) |
T |
? |
||||
2 |
\(+\) |
\(+\) |
T |
T |
F |
< |
\(-\) |
F |
F |
> |
|||
\(-\) |
\(+\) |
T |
T |
< |
||
\(-\) |
F |
> |
||||
3 |
\(+\) |
\(+\) |
F |
F |
T |
< |
\(-\) |
T |
|||||
\(-\) |
\(+\) |
F |
F |
> |
||
\(-\) |
T |
T |
||||
4 |
\(+\) |
\(+\) |
T |
T |
T |
= |
\(-\) |
F |
> |
||||
\(-\) |
\(+\) |
< |
||||
\(-\) |
T |
= |
||||
5 |
\(+\) |
\(+\) |
F |
F |
F |
? |
\(-\) |
< |
|||||
\(-\) |
\(+\) |
> |
||||
\(-\) |
? |
This table is translated to an algorithm. Unfortunatelly we don’t know which case the simples shapes are, so we will test by using some caracteristics.
For example, the first good information from the table is given by:
Case |
\(A\) |
\(B\) |
\(A\) in \(B\) |
\(J_A\) in \(B\) |
\(J_B\) in \(A\) |
\(a_A ? a_B\) |
|---|---|---|---|---|---|---|
1 |
\(-\) |
\(+\) |
F |
F |
T |
< |
2 |
\(-\) |
\(+\) |
F |
T |
T |
< |
3 |
\(-\) |
\(+\) |
F |
F |
F |
< |
4 |
\(-\) |
\(+\) |
F |
T |
T |
< |
5 |
\(-\) |
\(+\) |
F |
F |
F |
< |
# ...
shapea = SimpleShape(jordana)
shapeb = SimpleShape(jordanb)
# Decide if shapea in shapeb
if shapea.area < 0 and shapeb.area > 0:
# For any presented cases it happens
return False
# continue ...
Case |
\(A\) |
\(B\) |
\(A\) in \(B\) |
\(J_A\) in \(B\) |
\(J_B\) in \(A\) |
\(a_A ? a_B\) |
|---|---|---|---|---|---|---|
1 |
\(+\) |
\(-\) |
T |
T |
F |
> |
2 |
\(+\) |
\(-\) |
F |
F |
F |
> |
3 |
\(+\) |
\(-\) |
F |
T |
T |
> |
4 |
\(+\) |
\(-\) |
F |
T |
T |
> |
5 |
\(+\) |
\(-\) |
F |
F |
F |
> |
# ... continue
if shapea.area > 0 and shapeb.area < 0:
# Only for case 1
return (jordana in shapeb) and (jordanb not in shapea)
# continue ...
Taking out the already extracted values, and separating by when areaA > areaB:
Case |
\(A\) |
\(B\) |
\(A\) in \(B\) |
\(J_A\) in \(B\) |
\(a_A ? a_B\) |
|---|---|---|---|---|---|
1 |
\(+\) |
\(+\) |
F |
F |
> |
\(-\) |
\(-\) |
T |
|||
2 |
\(-\) |
\(-\) |
F |
F |
> |
3 |
\(+\) |
\(+\) |
F |
F |
> |
5 |
\(+\) |
\(+\) |
F |
F |
> |
\(-\) |
\(-\) |
Case |
\(A\) |
\(B\) |
\(A\) in \(B\) |
\(J_A\) in \(B\) |
\(a_A ? a_B\) |
|---|---|---|---|---|---|
1 |
\(+\) |
\(+\) |
F |
F |
<= |
\(-\) |
\(-\) |
T |
|||
2 |
\(+\) |
\(+\) |
T |
T |
< |
3 |
\(-\) |
\(-\) |
T |
T |
< |
4 |
\(+\) |
\(+\) |
T |
T |
= |
\(-\) |
\(-\) |
||||
5 |
\(+\) |
\(+\) |
F |
F |
<= |
\(-\) |
\(-\) |
# ... continue
if shapea.area > shapeb.area:
return False
# continue ...
We see that when \(J_A \ \text{in} \ B\) gives \(F\), the \(A \ \text{in} \ B\) is also \(F\)
# ... continue
if jordana not in shapeb:
return False
# continue ...
Rewriting the table we get
Case |
\(A\) |
\(B\) |
\(A\) in \(B\) |
\(J_A\) in \(B\) |
\(a_A ? a_B\) |
|---|---|---|---|---|---|
1 |
\(-\) |
\(-\) |
F |
T |
<= |
2 |
\(+\) |
\(+\) |
T |
T |
< |
3 |
\(-\) |
\(-\) |
T |
T |
< |
4 |
\(+\) |
\(+\) |
T |
T |
= |
\(-\) |
\(-\) |
Taking out when areaA > 0 we get
Case |
\(A\) |
\(B\) |
\(A\) in \(B\) |
\(J_A\) in \(B\) |
\(a_A ? a_B\) |
|---|---|---|---|---|---|
1 |
\(-\) |
\(-\) |
F |
T |
<= |
3 |
\(-\) |
\(-\) |
T |
T |
< |
4 |
\(-\) |
\(-\) |
T |
T |
= |
Boolean operations
The boolean operations are the main objective of this package. The following operations are available:
Name |
Logic |
Math |
Python |
|---|---|---|---|
Inversion |
NOT |
\(\overline{A}\) |
~A |
Union |
OR |
\(A \cup B\) |
A | B |
Intersection |
AND |
\(A \cap B\) |
A & B |
Subtraction |
SUB |
\(A - B\) |
A - B |
Exclusive or |
XOR |
\(A \otimes B\) |
A ^ B |
From these operations above, only three of them are basic: NOT, OR and AND.
The others are decomposed as follows:
SUB: A - B = A & (~B)
XOR: A ^ B = (A - B) | (B - A)
To recall, De Morgan’s law
~(A & B) = (~A) | (~B)
~(A | B) = (~A) & (~B)
A general table with all the operations
True and False entities
For this package, to represent the quantities :
Empty: False, void set
Whole: True, whole plane
Inversion / logic NOT
Union / logic OR
The union between two python boolean objects
from shapepy import Primitive
# Create two simple shapes
circle = Primitive.circle()
square = Primitive.square()
# Union
newshape = circle | square
Intersection / logic AND
The intersection between two python boolean objects
# Create two positive shapes
circle = section.shape.primitive.circle()
square = section.shape.primitive.square()
# Intersection
newshape = circle & square
Subtraction
The subtraction between two positive shapes means take out all part of \(A\) such is inside \(B\).
from shapepy import Primitive
# Create two positive shapes
circle = Primitive.circle()
square = Primitive.square()
# Subtract
newshape = circle - square
Exclusive union / logic XOR
The xor between two shapes. For this operator, we use the symbol ^.
# Create two positive shapes
circle = section.shape.primitive.circle()
square = section.shape.primitive.square()
# Subtract
newshape = circle ^ square
Generalities
One-dimensional integration
In the Integral section, the computation of the integral is needed:
When analitic integration is not used, then numerical integration takes place
The values of \(t_j\) and \(w_j\) are the nodes and weights of the quadrature schema. There are available schemas are bellow, with some nodes/weights depending on \(m\)
Closed Newton-Cotes
Open Newton-Cotes
Chebyshev
Gauss-Legendre
Closed Newton Cotes Quadrature
\(n\) |
\(x_i\) |
\(w_i\) |
|---|---|---|
2 |
0 |
1/2 |
1 |
1/2 |
|
3 |
0 |
1/6 |
0.5 |
4/6 |
|
1 |
1/6 |
Open Newton cotes Quadrature
\(n\) |
\(x_i\) |
\(w_i\) |
|---|---|---|
1 |
1/2 |
1 |
2 |
0 |
1/2 |
1 |
1/2 |
|
3 |
1/4 |
2/3 |
2/4 |
-1/3 |
|
3/4 |
2/3 |
Gaussian Quadrature
\(n\) |
\(x_i\) |
\(w_i\) |
|---|---|---|
1 |
1/2 |
1 |
Chebyshev Quadrature
\(n\) |
\(x_i\) |
\(w_i\) |
|---|---|---|
1 |
1/2 |
1 |
Lebesgue Density function
The Density function is a function on the plane, based on the a shape \(S\), that
Is equal to \(1\) for interior points
Is equal to \(0\) for exterior points
Is between \(0\) and \(1\) at the boundary
At the boundary, this function measures how much ‘convex’ the boundary is.
For smooth boundaries, like a straight line or the edges of a polygon, it is equal to \(0.5\).
At the corners of a square, it’s equal to \(0.25\), cause only 25% of the neighborhood is inside the square.
The formal definition is given by
If \((x, \ y)\) lies on the boundary, that means there’s a \(t^{\star}\) for a curve \(p\), and therefore
For smooth boundaries, \(p'\) is continuous at \(t^{\star}\). Meaning \(v_0 + v_1 = 0\) and then \(w_{S}(x, \ y) = 0.5\)