This package contains modules for working with basic geometric objects relating to translation surfaces and more generally to similarity surfaces.
Convex polygons in the plane (R^2)
This file implements convex polygons with
- action of matrices in GL^+(2,R)
- conversion between ground fields
EXAMPLES:
sage: from flatsurf.geometry.polygon import polygons
sage: K.<sqrt2> = NumberField(x^2 - 2, embedding=AA(2).sqrt())
sage: p = polygons((1,0), (-sqrt2,1+sqrt2), (sqrt2-1,-1-sqrt2))
sage: p
Polygon: (0, 0), (1, 0), (-sqrt2 + 1, sqrt2 + 1)
sage: M = MatrixSpace(K,2)
sage: m = M([[1,1+sqrt2],[0,1]])
sage: m * p
Polygon: (0, 0), (1, 0), (sqrt2 + 4, sqrt2 + 1)
A convex polygon in the plane RR^2 defined up to translation.
Return the angle at the begining of the start point of the edge e.
EXAMPLES:
sage: from flatsurf.geometry.polygon import polygons
sage: polygons.square().angle(0)
1/4
sage: polygons.regular_ngon(8).angle(0)
3/8
Return the area of this polygon.
EXAMPLES:
sage: from flatsurf.geometry.polygon import polygons
sage: polygons.regular_ngon(8).area()
2*a + 2
sage: _ == 2*AA(2).sqrt() + 2
True
sage: AA(polygons.regular_ngon(11).area())
9.36563990694544?
sage: polygons.square().area()
1
sage: (2*polygons.square()).area()
4
Return true if the point is within the polygon (after the polygon is possibly translated)
Flow a point in the direction of holonomy for the length of the holonomy, or until the point leaves the polygon. Note that ValueErrors may be thrown if the point is not in the polygon, or if it is on the boundary and the holonomy does not point into the polygon.
INPUT:
OUTPUT:
EXAMPLES:
sage: from flatsurf.geometry.polygon import polygons
sage: s = polygons.square()
sage: V=s.parent().vector_space()
sage: p=V((1/2,1/2))
sage: w=V((2,0))
sage: s.flow(p,w)
((1, 1/2), (3/2, 0), point positioned on interior of edge 1 of polygon)
Flow a point in the direction of holonomy until the point leaves the polygon. Note that ValueErrors may be thrown if the point is not in the polygon, or if it is on the boundary and the holonomy does not point into the polygon.
INPUT:
OUTPUT:
Get a combinatorial position of a points position compared to the polygon
INPUT:
OUTPUT:
EXAMPLES:
sage: from flatsurf.geometry.polygon import polygons
sage: s = polygons.square()
sage: V = s.parent().vector_space()
sage: s.get_point_position(V((1/2,1/2)))
point positioned in interior of polygon
sage: s.get_point_position(V((1,0)))
point positioned on vertex 1 of polygon
sage: s.get_point_position(V((1,1/2)))
point positioned on interior of edge 1 of polygon
sage: s.get_point_position(V((1,3/2)))
point positioned outside polygon
alias of ConvexPolygon
CachedMethodCallerNoArgs(inst, f, cache=None, name=None) File: sage/misc/cachefunc.pyx (starting at line 2098)
Utility class that is used by CachedMethod to bind a cached method to an instance, in the case of a method that does not accept any arguments except self.
Note
The return value None would not be cached. So, if you have a method that does not accept arguments and may return None after a lengthy computation, then @cached_method should not be used.
EXAMPLE:
sage: P.<a,b,c,d> = QQ[] sage: I = P*[a,b] sage: I.gens Cached version of <function gens at 0x...> sage: type(I.gens) <type 'sage.misc.cachefunc.CachedMethodCallerNoArgs'> sage: I.gens is I.gens True sage: I.gens() is I.gens() TrueAUTHOR:
- Simon King (2011-04)
Class for iteratively constructing a polygon over the field.
Add a vertex to the polygon. Returns 1 if successful and 0 if not, in which case the resulting polygon would not have been convex.
Class for describing the position of a point within or outside of a polygon.
alias of ConvexPolygons
EXAMPLES:
sage: from flatsurf.geometry.polygon import polygons
sage: polygons.rectangle(1,2)
Polygon: (0, 0), (1, 0), (1, 2), (0, 2)
sage: K.<sqrt2> = QuadraticField(2)
sage: polygons.rectangle(1,sqrt2)
Polygon: (0, 0), (1, 0), (1, sqrt2), (0, sqrt2)
sage: _.parent()
polygons with coordinates in Number Field in sqrt2 with defining
polynomial x^2 - 2
EXAMPLES:
sage: from flatsurf.geometry.polygon import is_opposite_direction
sage: V = QQ**2
sage: is_opposite_direction(V((0,1)), V((0,-2)))
True
sage: is_opposite_direction(V((1,-1)), V((-2,2)))
True
sage: is_opposite_direction(V((4,-2)), V((-2,1)))
True
sage: is_opposite_direction(V((-1,-2)), V((2,4)))
True
sage: is_opposite_direction(V((1,1)), V((1,2)))
False
sage: is_opposite_direction(V((1,2)), V((2,1)))
False
sage: is_opposite_direction(V((0,2)), V((0,1)))
False
sage: is_opposite_direction(V((1,2)), V((1,-2)))
False
sage: is_opposite_direction(V((1,2)), V((-1,2)))
False
sage: is_opposite_direction(V((2,-1)), V((-2,-1)))
False
sage: is_opposite_direction(V((1,0)), V.zero())
Traceback (most recent call last):
...
TypeError: zero vector has no direction
sage: for _ in range(100):
....: v = V.random_element()
....: if not v: continue
....: assert not is_opposite_direction(v, v)
....: assert not is_opposite_direction(v,2*v)
....: assert is_opposite_direction(v, -v)
EXAMPLES:
sage: from flatsurf.geometry.polygon import is_same_direction
sage: V = QQ**2
sage: is_same_direction(V((0,1)), V((0,2)))
True
sage: is_same_direction(V((1,-1)), V((2,-2)))
True
sage: is_same_direction(V((4,-2)), V((2,-1)))
True
sage: is_same_direction(V((1,2)), V((2,4)))
True
sage: is_same_direction(V((0,2)), V((0,1)))
True
sage: is_same_direction(V((1,1)), V((1,2)))
False
sage: is_same_direction(V((1,2)), V((2,1)))
False
sage: is_same_direction(V((1,2)), V((1,-2)))
False
sage: is_same_direction(V((1,2)), V((-1,-2)))
False
sage: is_same_direction(V((2,-1)), V((-2,1)))
False
sage: is_same_direction(V((1,0)), V.zero())
Traceback (most recent call last):
...
TypeError: zero vector has no direction
sage: for _ in range(100):
....: v = V.random_element()
....: if not v: continue
....: assert is_same_direction(v, 2*v)
....: assert not is_same_direction(v, -v)
The native Sage function number_field_elements_from_algebraics currently returns number field without embedding. This function return field with embedding!
EXAMPLES:
sage: from flatsurf.geometry.polygon import number_field_elements_from_algebraics
sage: z = QQbar.zeta(5)
sage: c = z.real()
sage: s = z.imag()
sage: number_field_elements_from_algebraics((c,s))
(Number Field in a with defining polynomial y^4 - 5*y^2 + 5,
[1/2*a^2 - 3/2, 1/2*a])
Translation surfaces.
Abstract base class for origamis. Realization needs just to define a _domain and four cardinal directions.
We label copy by cartesian product (polygon from bot, matrix).
An oriented surface built from a set of polygons and edges identified with similarities (i.e. composition of homothety, rotations and translations).
Each polygon is identified with a unique key (its label). The choice of the label of the polygons is done at startup. If the set is finite then by default the labels are the first non-negative integers 0,1,...
The edge are identified by a couple (polygon label, edge number).
Note
This class is abstract and should not be called directly. Instead you can either use SimilaritySurface_from_polygons_and_identifications or inherit from it and implement the methods:
It might also be good to implement
- base_polygon(self): a couple (label, polygon) that is a somewhat fixed polygon
- opposite_edge(self, lab, edege): a couple (other_lab, other_edge)
The field on which the coordinates of self live.
This method must be overriden in subclasses!
Return the edge to which this edge is identified and the matrix to be applied.
Return the similarity bringing the provided edge to the opposite edge.
EXAMPLES:
sage: from flatsurf.geometry.similarity_surface_generators import SimilaritySurfaceGenerators
sage: s = SimilaritySurfaceGenerators.example()
sage: print(s.polygon(0))
Polygon: (0, 0), (2, -2), (2, 0)
sage: print(s.polygon(1))
Polygon: (0, 0), (2, 0), (1, 3)
sage: print(s.opposite_edge(0,0))
(1, 1)
sage: g = s.edge_transformation(0,0)
sage: g((0,0))
(1, 3)
sage: g((2,-2))
(2, 0)
Return a basis for the fundamental group as a sequence of paths:
[vertex0, edge0, vertex1, edge1, ...].
Return a pair (sm,sb), where sm is the active SurfaceManipulator, and sb is the surface bundle for this surface (which is added if neccessary to the SurfaceManipulator). If necessary, we create one or both objects.
Return the minimal translation cover.
Be careful that if the surface is not built from one polygon, this is not the smallest translation cover of the surface.
EXAMPLES:
sage: from flatsurf import *
sage: S = similarity_surfaces.example()
sage: T = S.minimal_translation_cover()
sage: T
Translation surface built from +Infinity polygons
sage: T.polygon(T.polygon_labels().an_element())
Polygon: (0, 0), (8/5, -4/5), (6/5, 2/5)
Given the label p of a polygon and an edge e in that polygon returns the pair (pp, ee) to which this edge is glued.
This method must be overriden in subclasses.
Return the polygon with label lab.
This method must be overriden in subclasses.
The set of labels used by the polygons.
This method must be overriden in subclasses.
Return the tangent bundle
INPUT:
Return a tangent vector.
INPUT:
EXAMPLES:
sage: from flatsurf.geometry.chamanara import ChamanaraSurface
sage: S = ChamanaraSurface(1/2)
sage: S.tangent_vector(0, (1/2,1/2), (1,1))
SimilaritySurfaceTangentVector in polygon 1 based at (-1/2, 3/2) with vector
(-1, -1)
sage: K.<sqrt2> = QuadraticField(2)
sage: S.tangent_vector(0, (1/2,1/2), (1,sqrt2))
SimilaritySurfaceTangentVector in polygon 1 based at (-1/2, 3/2) with vector
(-1, -sqrt2)
sage: S = ChamanaraSurface(sqrt2/2)
sage: S.tangent_vector(1, (0,1), (1,1))
SimilaritySurfaceTangentVector in polygon 0 based at (-sqrt2, sqrt2
+ 1) with vector (-1, -1)
Similarity surface build from a list of polygons and gluings.
A surface with a flat metric and conical singularities (not necessarily multiple angle of pi or 2pi).
A translation surface is:
For finite case:
Some tools for 2x2 matrices and planar geometry.
Return the angle between the vectors u and v divided by 2 pi.
INPUT:
EXAMPLES:
sage: from flatsurf.geometry.matrix_2x2 import angle
As the implementation is dirty, we at least check that it works for all denominator up to 20:
sage: u = vector((AA(1),AA(0)))
sage: for n in xsrange(1,20): # long time (10 sec)
....: for k in xsrange(1,n):
....: v = vector((AA(cos(2*k*pi/n)), AA(sin(2*k*pi/n))))
....: assert angle(u,v) == k/n
And we test up to 50 when setting assume_rational to True:
sage: for n in xsrange(1,50): # long time (25 sec)
....: for k in xsrange(1,n):
....: v = vector((AA(cos(2*k*pi/n)), AA(sin(2*k*pi/n))))
....: assert angle(u,v,assume_rational=True) == k/n
If the angle is not rational, then the method returns an element in the real lazy field:
sage: v = vector((AA(sqrt(2)), AA(sqrt(3))))
sage: a = angle(u,v)
sage: a
0.1410235542122437?
sage: exp(2*pi.n()*CC(0,1)*a.n())
0.632455532033676 + 0.774596669241483*I
sage: v / v.norm()
(0.6324555320336758?, 0.774596669241484?)
Return a couple composed of the homothety and a rotation matrix.
The coefficients of the returned pair are either in the ground field of m or in the algebraic field AA.
EXAMPLES:
sage: from flatsurf.geometry.matrix_2x2 import homothety_rotation_decomposition
sage: R.<x> = PolynomialRing(QQ)
sage: K.<sqrt2> = NumberField(x^2 - 2, embedding=1.4142)
sage: m = matrix([[sqrt2, -sqrt2],[sqrt2,sqrt2]])
sage: a,rot = homothety_rotation_decomposition(m)
sage: a
2
sage: rot
[ 1/2*sqrt2 -1/2*sqrt2]
[ 1/2*sqrt2 1/2*sqrt2]
Check whether the given pair is a cosine and sine of a same rational angle.
EXAMPLES:
sage: from flatsurf.geometry.matrix_2x2 import is_cosine_sine_of_rational
sage: c = s = AA(sqrt(2))/2
sage: is_cosine_sine_of_rational(c,s)
True
sage: c = AA(sqrt(3))/2; s = AA(1/2)
sage: is_cosine_sine_of_rational(c,s)
True
sage: c = AA(sqrt(5)/2); s = (1 - c**2).sqrt()
sage: c**2 + s**2
1.000000000000000?
sage: is_cosine_sine_of_rational(c,s)
False
sage: c = (AA(sqrt(5)) + 1)/4; s = (1 - c**2).sqrt()
sage: is_cosine_sine_of_rational(c,s)
True
Return True if m is a similarity and False otherwise.
EXAMPLES:
sage: from flatsurf.geometry.matrix_2x2 import is_similarity
sage: is_similarity(matrix([[0,1],[1,0]]))
True
sage: is_similarity(matrix([[0,-2],[2,0]]))
True
sage: is_similarity(matrix([[1,1],[0,1]]))
False
It is a mess to convert an element of a number field to the algebraic field AA. This is a temporary fix.
Return the angle of the rotation matrix r divided by 2 pi.
EXAMPLES:
sage: from flatsurf.geometry.matrix_2x2 import rotation_matrix_angle
sage: def rot_matrix(p, q):
....: z = QQbar.zeta(q) ** p
....: c = z.real()
....: s = z.imag()
....: return matrix(AA, 2, [c,-s,s,c])
sage: [rotation_matrix_angle(rot_matrix(i, 5)) for i in range(1,5)]
[1/5, 2/5, 3/5, 4/5]
sage: [rotation_matrix_angle(rot_matrix(i,7)) for i in range(1,7)]
[1/7, 2/7, 3/7, 4/7, 5/7, 6/7]
Some random tests:
sage: for _ in range(100):
....: r = QQ.random_element(x=0,y=500)
....: r -= r.floor()
....: m = rot_matrix(r.numerator(), r.denominator())
....: assert rotation_matrix_angle(m) == r
Note
This is using floating point arithmetic and might be wrong.
Return the unique similarity matrix that maps u to v.
EXAMPLES:
sage: from flatsurf.geometry.matrix_2x2 import similarity_from_vectors
sage: V = VectorSpace(QQ,2)
sage: u = V((1,0))
sage: v = V((0,1))
sage: m = similarity_from_vectors(u,v); m
[ 0 -1]
[ 1 0]
sage: m*u == v
True
sage: u = V((2,1))
sage: v = V((1,-2))
sage: m = similarity_from_vectors(u,v); m
[ 0 1]
[-1 0]
sage: m * u == v
True
An example built from the Pythagorean triple 3^2 + 4^2 = 5^2:
sage: u2 = V((5,0))
sage: v2 = V((3,4))
sage: m = similarity_from_vectors(u2,v2); m
[ 3/5 -4/5]
[ 4/5 3/5]
sage: m * u2 == v2
True
Some test over number fields:
sage: K.<sqrt2> = NumberField(x^2-2, embedding=1.4142)
sage: V = VectorSpace(K,2)
sage: u = V((sqrt2,0))
sage: v = V((1, 1))
sage: m = similarity_from_vectors(u,v); m
[ 1/2*sqrt2 -1/2*sqrt2]
[ 1/2*sqrt2 1/2*sqrt2]
sage: m*u == v
True
sage: m = similarity_from_vectors(u, 2*v); m
[ sqrt2 -sqrt2]
[ sqrt2 sqrt2]
sage: m*u == 2*v
True
Class for a similarity of the plane.
Group representing all similarities in the plane. This is the group generated by rotations, translations and dilations.
Construct the tangent bundle of a given similarity surface.
Needs work: We should check for coersion from the base_ring of the surface
Return the vector leaving a vertex of the polygon which under straight-line flow travels clockwise around the boundary of the polygon along the edge with the provided index. The length of the vector matches the length of the indexed edge. Note that the point will be based in the polgon opposite the provided edge.
EXAMPLES:
sage: from flatsurf.geometry.similarity_surface_generators import SimilaritySurfaceGenerators
sage: s = SimilaritySurfaceGenerators.example()
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentBundle
sage: tb = SimilaritySurfaceTangentBundle(s)
sage: print("Polygon 0 is "+str(s.polygon(0)))
Polygon 0 is Polygon: (0, 0), (2, -2), (2, 0)
sage: print("Polygon 1 is "+str(s.polygon(1)))
Polygon 1 is Polygon: (0, 0), (2, 0), (1, 3)
sage: print("Opposite edge to (0,0) is "+repr(s.opposite_edge(0,0)))
Opposite edge to (0,0) is (1, 1)
sage: print(tb.clockwise_edge(0,0))
SimilaritySurfaceTangentVector in polygon 1 based at (2, 0) with vector (-1, 3)
Return the vector leaving a vertex of the polygon which under straight-line flow travels counterclockwise around the boundary of the polygon along the edge with the provided index. The length of the vector matches the length of the indexed edge.
EXAMPLES:
sage: from flatsurf.geometry.similarity_surface_generators import SimilaritySurfaceGenerators
sage: s = SimilaritySurfaceGenerators.example()
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentBundle
sage: tb = SimilaritySurfaceTangentBundle(s)
sage: print(s.polygon(0))
Polygon: (0, 0), (2, -2), (2, 0)
sage: print(tb.edge(0,0))
SimilaritySurfaceTangentVector in polygon 0 based at (0, 0) with vector (2, -2)
Returns true if the other vector just differs by scaling. This means they should lie in the same polygon, be based at the same point, and point in the same direction.
Returns the pair of (p,e) where p is the polygon label at the base point, and e is the edge this vector points along or none if it does not point along an edge. Here pointing along means that the vector is based at a vertex and represents the vector joining this edge to the next vertex.
Flows forward (in the direction of the tangent vector) until the end of the polygon is reached. Returns the tangent vector based at the endpoint which point backward along the trajectory.
NOTES:
We return the backward trajectory, because continuing forward does not make sense if a
singularity is reached. You can obtain the forward vector by subsequently applying invert().
EXAMPLES:
sage: from flatsurf.geometry.similarity_surface_generators import SimilaritySurfaceGenerators
sage: s = SimilaritySurfaceGenerators.example()
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentBundle
sage: tb = SimilaritySurfaceTangentBundle(s)
sage: print("Polygon 0 is "+str(s.polygon(0)))
Polygon 0 is Polygon: (0, 0), (2, -2), (2, 0)
sage: print("Polygon 1 is "+str(s.polygon(1)))
Polygon 1 is Polygon: (0, 0), (2, 0), (1, 3)
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentVector
sage: V = tb.surface().vector_space()
sage: v = SimilaritySurfaceTangentVector(tb, 0, V((0,0)), V((3,-1)))
sage: print(v)
SimilaritySurfaceTangentVector in polygon 0 based at (0, 0) with vector (3, -1)
sage: v2 = v.forward_to_polygon_boundary()
sage: print(v2)
SimilaritySurfaceTangentVector in polygon 0 based at (2, -2/3) with vector (-3, 1)
sage: print(v2.invert())
SimilaritySurfaceTangentVector in polygon 1 based at (2/3, 2) with vector (4, -3)
Returns the negation of this tangent vector. Raises a ValueError if the vector is based at a singularity.’
Return the truth value of the statement ‘the base point for this vector is a singularity.’
Stores a maximal segment in a polygon of a translation surface.
Return the next segment obtained by continuing straight through the end point.
EXAMPLES:
sage: from flatsurf.geometry.similarity_surface_generators import SimilaritySurfaceGenerators
sage: s = SimilaritySurfaceGenerators.example()
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentBundle
sage: tb = SimilaritySurfaceTangentBundle(s)
sage: print("Polygon 0 is "+str(s.polygon(0)))
Polygon 0 is Polygon: (0, 0), (2, -2), (2, 0)
sage: print("Polygon 1 is "+str(s.polygon(1)))
Polygon 1 is Polygon: (0, 0), (2, 0), (1, 3)
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentVector
sage: V = tb.surface().vector_space()
sage: v = SimilaritySurfaceTangentVector(tb, 0, V((0,0)), V((3,-1)))
sage: from flatsurf.geometry.straight_line_trajectory import *
sage: seg = SegmentInPolygon(v)
sage: print(seg)
Segment in polygon 0 starting at (0, 0) and ending at (2, -2/3)
sage: from flatsurf.geometry.similarity_surface_generators import SimilaritySurfaceGenerators
sage: s = SimilaritySurfaceGenerators.example()
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentBundle
sage: tb = SimilaritySurfaceTangentBundle(s)
sage: print("Polygon 0 is "+str(s.polygon(0)))
Polygon 0 is Polygon: (0, 0), (2, -2), (2, 0)
sage: print("Polygon 1 is "+str(s.polygon(1)))
Polygon 1 is Polygon: (0, 0), (2, 0), (1, 3)
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentVector
sage: V = tb.surface().vector_space()
sage: v = SimilaritySurfaceTangentVector(tb, 0, V((0,0)), V((3,-1)))
sage: from flatsurf.geometry.straight_line_trajectory import *
sage: seg = SegmentInPolygon(v)
sage: print(seg)
Segment in polygon 0 starting at (0, 0) and ending at (2, -2/3)
sage: print(seg.next())
Segment in polygon 1 starting at (2/3, 2) and ending at (14/9, 4/3)
Abstract class for a straight-line trajectory.
Append or preprend segments to the trajectory. If steps is positive, attempt to append this many segments. If steps is negative, attempt to prepent this many segments. Will fail gracefully the trajectory hits a singularity or closes up.
EXAMPLES:
sage: from flatsurf.geometry.similarity_surface_generators import SimilaritySurfaceGenerators
sage: s = SimilaritySurfaceGenerators.example()
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentBundle
sage: tb = SimilaritySurfaceTangentBundle(s)
sage: print("Polygon 0 is "+str(s.polygon(0)))
Polygon 0 is Polygon: (0, 0), (2, -2), (2, 0)
sage: print("Polygon 1 is "+str(s.polygon(1)))
Polygon 1 is Polygon: (0, 0), (2, 0), (1, 3)
sage: from flatsurf.geometry.tangent_bundle import SimilaritySurfaceTangentVector
sage: V = tb.surface().vector_space()
sage: v = SimilaritySurfaceTangentVector(tb, 0, V((1,-0.5)), V((3,-1)))
sage: from flatsurf.geometry.straight_line_trajectory import *
sage: traj = StraightLineTrajectory(v)
sage: print(traj)
StraightLineTrajectorydeque([Segment in polygon 0 starting at (1/4, -1/4) and ending at (2, -5/6)])
sage: traj.flow(1)
sage: print(traj)
StraightLineTrajectorydeque([Segment in polygon 0 starting at (1/4, -1/4) and ending at (2, -5/6), Segment in polygon 1 starting at (7/12, 7/4) and ending at (61/36, 11/12)])
sage: traj.flow(-1)
sage: print(traj)
StraightLineTrajectorydeque([Segment in polygon 1 starting at (15/16, 45/16) and ending at (9/8, 21/8), Segment in polygon 0 starting at (1/4, -1/4) and ending at (2, -5/6), Segment in polygon 1 starting at (7/12, 7/4) and ending at (61/36, 11/12)])
This module contains a lazy implementation of a relative homology, $H_1(S,Sigma; R)$, where $S$ is a similarity surface, $Sigma$ is the singularities or vertices, and $R$ is a ring.
This implementation works for finite or infinite surfaces. For infinite surfaces, we define relative homology formally. It is simply $R^E$ where $E$ is the edge set modulo equivalences of two types: 1) If $e$ is an edge, and $e’$ is its opposite edge oriented counterclockwise from the polygon they bound then $e+e’=0$ in homology. 2) The sum of edges around a polygon is zero.
alias of RelativeHomologyClass