)if false
\documentclass{article}
\usepackage{axiom}
\usepackage{url}
\begin{document}
\title{Algebraic Topology Structures}
\author{Martin J Baker}
\maketitle
\begin{abstract}

This code allows us to compute with topological spaces in a finite
way.

This is implemented as a number of related domains. As usual I guess
we have to clarify definitions. Hatcher[9] (p102) gives delta
complexes as a "mild generalization of the more classical notion of
a simplicial complex". This "mild generalization" is allowing multiple
copies of facets. For instance, instead of the minimal triangulation
of a projective plane having 10 triangles it can be represented as a
square (2 triangles) where the opposite edges are the same (but with
opposite orientation).

Some of the domains we will define are:
\begin{itemize}
\item FiniteSimplicialComplex - n-dimensional triangles indexed by
their vertices.
\item FiniteCubicalComplex - n-dimensional squares indexed by their
vertices.
\item DeltaComplex - graded structure indexed is in terms of next
lower dimension.
\item (co)ChainComplex - Holds same information as DeltaComplex but in an
algebraic form rather than a geometric form, that is as a sequence
of matrices.
\end{itemize}

There are also a lot of other domains here used to construct these.

To generate (co)homology from FiniteSimplicialComplex or
FiniteCubicalComplex we:
\begin{itemize}
\item generate DeltaComplex from FiniteSimplicialComplex or
      FiniteCubicalComplex
\item generate (co)ChainComplex from DeltaComplex
\item generate (co)homology from (co)ChainComplex
\end{itemize}

So I have added homology functions to DeltaComplex, FiniteSimplicialComplex
and FiniteCubicalComplex just so end users can avoid having to
explicitly do these steps.

I have put a fuller explanation of this code here:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/}
\end{abstract}

\section{Related Code}

Simplicial Complexes are a generalisation of graph theory as
implemented here:
\url{https://github.com/fricas/fricas/blob/master/src/algebra/graph.spad}
There are coercions from graphs to simplicial complexes.

The ChainComplex is a sequence of matrices, in this linear algebra form
the homology groups are calculated from the Smith normal form:
\url{https://github.com/fricas/fricas/blob/master/src/algebra/smith.spad}
\url{https://github.com/fricas/fricas/blob/master/src/algebra/smith2.spad}

This linear algebra form seems somehow related to regular triangular sets
(regular chains) not exactly sure how?
\url{https://github.com/fricas/fricas/blob/master/src/algebra/regset.spad}
\url{https://github.com/fricas/fricas/blob/master/src/algebra/sregset.spad}
\url{https://github.com/fricas/fricas/blob/master/src/algebra/zerodim.spad}

Zariski topology:
The Zariski topology of an algebraic variety is the topology whose
closed sets are the algebraic subsets of the variety.
\url{https://github.com/fricas/fricas/blob/master/src/algebra/zerodim.spad}
\url{https://github.com/fricas/fricas/blob/master/src/algebra/regset.spad}

The cohomology/cochain implemented here is discrete as opposed to the
DeRhamComplex which uses differential forms. So if we were computing
say, electrical circuits, then cochain might work with Kirchhoff's
circuit laws whereas DeRhamComplex might work with the Maxwell equations.
Other cohomology domains:
DeRhamComplex:
\url{https://github.com/fricas/fricas/blob/master/src/algebra/derham.spad}
DifferentialGeometry
\url{http://wiki.fricas.org/SandBoxDifferentialGeometry}

\section{Introduction}

A 'simplex' is a set of vertices such that every subset is included
in the simplex. For example a tetrahedron contains all its faces,
lines and points.

A 'simplicial complex' is a set of these simplexes which may have
vertices in common.

Because each simplex contains its subsets, this is a combinatorial
structure. When used in this way the structure is known as an
'abstract simplicial complex' optionally we can also associate a
point in a space with each vertex, in this case the structure is
known as an 'geometric simplicial complex' and it has
geometric/topological/homotopy properties.

It is intended that this code is able to be able to support both
abstract and geometric simplicial complexes.

Three types of cell complex are implemented:
\begin{itemize}
\item SimpectialComplex
\item DeltaComplex
\item CubicalComplex
\end{itemize}

\section{Simplicial Complexes}

I have put some examples of Simplicial Complexes on this webpage:
http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/

Defines a set of n-dimensional facets.

This representation defines faces, of any dimension, by indexing their
vertices. This can be an efficient way to define them.

As an example consider the representation for a single tetrahedron:
The representation would be: (1, 2, 3, 4)

If the dimension given for the simplicial complex is 'k' then:
\begin{itemize}
\item The number of elements in each vertex = k.
\item The maximum number of vertices in each simplice = k + 1.
\end{itemize}
For example, a triangle has 3 vertices, so it is
maximum size face in 3-1=2 dimensions.
\begin{table}[]
\label{Dimensions}
\begin{tabular}{lll}
dimension \ vertex \ simplice - (faces) \\
0 \ 0 elements \ point \\
1 \ 1 element vertex \ line - (edge) \\
2 \ 2 element vertex \ triangle \\
3 \ 3 element vertex \ tetrahedron \\
n \ n element vertex \ simplice \\
\end{tabular}
\end{table}

\section{Abstract Simplicial Complex vs. Simplicial Complex}
If we apply the restrictions explained so far we have an
abstract simplicial complex,
However, for a simplicial complex there are some additional conditions.

An Abstract Simplicial Complex is purely combinitorial,
that is we don't need the geometric information.

Therefore the AbstractSimplicialComplex domain does not
need coordinates for the vertices and they can be denoted by symbols.

\section{Operations on Simplicial Complexes}

\begin{itemize}
\item Closure - closure of X contains X and all the faces touching X
\item Star(A, b) - set of sub-simplices of 'A' which contain face 'b'.
\item Link - The 'link' of a simplicial complex and a vertex contains
 the boundary of the simplexes of s which include simplex.
\item Join - We will call it a SimplicialJoin since it is not related
  to joins in lattices.
\end{itemize}
More information about these constructs on this page:
http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/join/

\section{Oriented Facets Category}

The aim of this category is to represent different types of facet,
such as,
\begin{itemize}
\item A single simplex.
\item A single cubical facet.
\item The product of two facets
\end{itemize}
These all have similar functions but different representations.

The first of these is OrientedFacet which is the usual triangle and
higher-order-triangle based facet, it holds the facet as a set of
indexes (fac) together with an orientation (mul) like this:
Rep := Record(mul : Integer, fac : List(NNI))

The second is a cubical facet, this is held as a set of indexed
line intervals which may, or may not, be degenerate.
Rep := Record(mul : Integer, fac : List(Segment(Integer)))

The third is a product facet, this is intended to hold the product of
two facets. The product has two indexes to represent each vertex, that
is the Cartesian product of the vertices of the operands. Usually this
double vertex will be normalised back to a single set of indexes but
this can only be done after the whole simplicial complex has been
multiplied so the corresponding facets can be matched up and sorted.
So the representation is:
Rep := Record(mul : Integer, ind : List(Record(left : NNI, right : NNI)))

OrderedSet category is implemented so we can sort lists of facets. This
requires that implementations support 'less than' and 'equal'
(<,<= and =) functions.
)endif

)abbrev category FACTCAT FacetCategory
++ Author: Martin Baker
++ Date Created: April 2016
++ Basic Operations:
++ Related Constructors:
++ Keywords: facet simplex
++ Description:
++  This is a category to represent different types of facet, such as,
++  (1) A single simplex
++  (2) A single cubical simplex
++  (3) The product of two facets
++
++ References:
++ http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/

FacetCategory() : Category == Definition where
 NNI==> NonNegativeInteger

 Definition ==> Join(SetCategory(), OrderedSet()) with
   getMult : (orf : %) -> Integer
     ++ returns multiplier of face
   order : (orf : %) -> NNI
     ++ number of vertices
   empty? : (sa : %) -> Boolean
     ++ is empty if it contains no vertices or if multiplier is zero

  add

   -- is empty if it contains no vertices or if multiplier is zero
   empty?(orf : %) : Boolean ==
       if order(orf) = 0 then return true
       getMult(orf) = 0

)if false
\section{Oriented Facet}

This domain holds a single facet, that is a single vertex, edge,
triangle...
The FiniteSimplicialComplex will then contain multiple oriented Facets.
See FiniteSimplicialComplex section for more information about that.

I have found it worthwhile and efficient to gather here all the
operations on a single facet rather than trying to do everything
inside FiniteSimplicialComplex.

OrientedFacet holds an (ordered) set of vertices not the geometric
vertices themselves, but indexes which can be used to reference them.

It also holds an orientation, which is held in 'mul'. When we are
interpreting this as a geometric object it is usually 1 or -1 (to
reverse direction). When we are interpreting this as a linear algebra
object then we treat it as an integer.

To allow facets to be checked for equality indexes are stored in order
)endif

)abbrev domain OFACET OrientedFacet
++ Author: Martin Baker
++ Description:
++   A single vertex, edge, triangle...
++   for more documentation see:
++   http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/
++ Date Created: March 2016
++ Basic Operations:
++ Related packages:
++ Related categories: FacetCategory
++ Related Domains:
++ Also See:
++ AMS Classifications:
++ Keywords:
++ Examples:
++ References:

OrientedFacet() : Exports == Impl where
  NNI==> NonNegativeInteger
  x<<y ==> hconcat(x::OutputForm, y::OutputForm)

  Exports ==> FacetCategory with
    orientedFacet : (mul : Integer, fac : List(NNI)) -> %
      ++ Constructor for oriented facet
      ++ oriented facet is always stored in vertex order
      ++ orientation is calculated by the number of edges that have
      ++ to be flipped to put it in order.
    orientedFacetUnsigned : (fac : List(Integer)) -> %
      ++ Constructor for oriented face which removes duplicates
      ++ and ignores orientation
    orientedFacetSigned : (fac : List(Integer)) -> %
      ++ Constructor for oriented face which removes duplicates
      ++ If the face is positive then we expect the sequence to
      ++ be: + - + - ...
    orientedFacet : (mul : Integer, fac : OrientedFacet) -> %
      ++ Copy constructor which can change mul
    orientedFacetIfCan : (boundary : List(%)) -> Union(%, "failed")
      ++ Constructor for oriented face from its boundary.
      ++ This is like filling-in the boundary, in that sense it
      ++ is the inverse of 'boundary'.
      ++ May fail, for instance in 'implied' loops, where both ends
      ++ of an edge are the same point. Also similar cases for higher
      ++ order facets.
    getIndexes : (orf : %) -> List(NNI)
      ++ returns indexes in face
    minIndex : (orf : %) -> NNI
      ++ returns minimum index
    maxIndex : (orf : %) -> NNI
      ++ returns maximum index
    getIndexesSigned : (orf : %) -> List(Integer)
      ++ returns indexes in face with sign to give orientation
    setMult! : (orf : %, m : Integer) -> %
      ++ sets multiplier of face
    boundary : (orf : %) -> List(%)
      ++ construct the boundary. This is like hollowing out
      ++ the facet.
    allSubsets : (orf : %, minDim : NNI) -> List(%)
      ++ allSubsets of a given facet
      ++ returned in reverse order
      ++ subsets returned are not oriented
    allSubsets : (orf : %, minDim : NNI, maxDim : NNI) -> List(%)
      ++ allSubsets of a given length
      ++ returned in reverse order
      ++ subsets returned are not oriented
    isSubsetOf? : (sa : %, sb : %) -> Boolean
      ++ returns true if face 'a' is contained in face 'b'.
      ++ true subset only : returns false if a=b
    position : (e : %, lst : List(%)) -> Integer
      ++ return index of e in lst. Index will be negative if e has
      ++ negative multiplier.
    sameFace? : (a : %, b : %) -> Boolean
      ++ true if this is the same face although the orientation
      ++ may be different
    join : (sa : %, sb : %) -> %
      ++ used by simplicialJoin. Returns join (union) of 'a' and 'b'.
      ++ That is: returns a facet containing indexes in both 'a' and 'b'.
    subtract : (sa : %, sb : %) -> %
      ++ used by link. Returns face 'a' minus the vertices in face 'b'.
    reindex : (sa : %) -> %
      ++ re-index so that indexes start at 1 and are then consecutive.
    boolWordPermute : (numZeros : NNI, numOnes : NNI)->List(List(Boolean))
      ++ boolean word permutation
      ++ find all permutations of numZeros and numOnes
      ++ used by product to calculate all paths through lattice
    boolWordPermute : (wordIn : List(List(Boolean)), numZeros : NNI,
                       numOnes : NNI)->List(List(Boolean))
      ++ recursive part of boolWordPermute
    product : (a : %, b : %) -> List(ProductFacet)
      ++ This function returns the product of two facets.
      ++ For more detailed explanation of simplex product code see:
      ++ \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/product/}
      ++ Also see Hatcher [9].
      ++ page 277 - The simplicial cross product.
      ++ page 278 - The cohomology cross product.
      ++ Returns list, each entry is a 'lattice path' from (0, 0) to (x, y)
      ++ where x is number of entries in sa and y is number of entries in sb.
      -- Calculation of 'lattice paths' is a combinatorics problem so
      -- perhaps it should really be done by combinatorics code?
    addVertex : (sa : %, v : NNI) -> %
      ++ used by cone. Adds a vertex to face.
    refactorIndexes : (a : %, n : NNI) -> %
      ++ adds 'n' to each index. Used to refactor simplexes so that
      ++ they don't overlap.

  Impl ==> add

   -- A facet is a list of indexes (ordered)
   -- 'mul' encodes the orientation. When we are interpreting this
   -- as a geometric object it is usually 1 or -1 (to reverse direction).
   -- When we are interpreting this as a linear algebra object then
   -- we treat it as an integer.
   Rep := Record(mul : Integer, fac : List(NNI))

   -- Constructor for oriented facet
   -- oriented facet is always stored in vertex order
   -- orientation is calculated by the number of edges that have to be
   -- flipped to put it in order.
   orientedFacet(mul1 : Integer, fac1 : List(NNI)) : % ==
       --print("orientedFacet(" << mul1 << ", " << fac1)
       mul2 : Integer := mul1
       fac2 : List(NNI) := sort(fac1)
       -- diff = number of differences between sorted and unsorted
       diff : NNI := 0
       for x in fac1 for y in fac2 repeat
           if x ~= y then diff := diff + 1
       -- swapping vertices cannot change only 1
       if diff = 1 then error "can not construct orientedFacet"
       if even?(diff) and diff > 1 then mul2 := -mul2
       --print("orientedFacet(" << mul1 << ", " << fac1 << _
       --  ") mul2=" << mul2 << " fac2=" << _
       --  fac2 << " diff=" << diff)
       [mul2, fac2]

   -- constructor for oriented face which removes duplicates
   -- and ignores orientation
   orientedFacetUnsigned(fac1 : List(Integer)) : % ==
       lst : List(NNI) := [abs(x)::NNI for x in fac1]
       lst := removeDuplicates(lst)
       [1, sort(lst)]

   -- Constructor for oriented face which removes duplicates.
   -- If the face is positive then we expect the sequence to
   -- be: + - + - ...
   orientedFacetSigned(fac1 : List(Integer)) : % ==
       lst : List(NNI) := [abs(x)::NNI for x in fac1]
       lst := removeDuplicates(lst)
       sig : Integer := 1
       if #fac1 < 1 then error "attempt to construct empty facet"
       if first(fac1) < 0 then sig := -1
       [sig, sort(lst)]

   -- Copy constructor which can change mul
   orientedFacet(mul : Integer, fac1 : OrientedFacet) : % ==
       [mul, getIndexes(fac1)]

   -- Constructor for oriented face from its boundary.
   -- This is like filling-in the boundary, in that sense it
   -- is the inverse of 'boundary'.
   orientedFacetIfCan(boundary : List(%)) : Union(%, "failed") ==
       -- print("orientedFacet:" << boundary)
       indexLst := []$List(List(NNI))
       if #boundary < 1 then return "failed"
       b : List(%) := sort(boundary)
       --print("orientedFacet b=" << b << " boundary="
       --       << boundary)
       for subFacet in b repeat
           indexLst := concat(indexLst, getIndexes(subFacet))
       --print("orientedFacet indexLst=" << indexLst)
       indexes := []$List(NNI)
       for i in indexLst repeat
           indexes := concat(indexes, i)
       indexes := removeDuplicates(indexes)
       indexes := sort(indexes)
       res := orientedFacet(1, indexes)
       expected : List(%) := boundary(res)
       mm := position(first(boundary), expected)
       if mm < 0 then
           res := orientedFacet(-1, indexes)
           expected := boundary(res)
       -- the remainder of this is to validate that boundary supplied really
       -- is a boundary for the facet that has been generated
       for e in expected repeat
           if (mm := position(e, boundary)) <= 0 then
               --print(message("orientedFacet:") << e <<
               --      message(mm = 0 => "not in provided boundary";
               --       "has opposite orientation"))
               return "failed"
       for e in boundary repeat
           if (mm := position(e, expected)) <= 0 then
               --print(message("orientedFacet:") << e <<
               --      message(mm = 0 => "not in constructed boundary";
               --       "has opposite orientation"))
               return "failed"
       res

   -- number of vertices
   order(f : %) : NNI ==
       facet : List(NNI) := getIndexes(f)
       #facet

   -- returns indexes in face
   getIndexes(orf : %) : List(NNI) ==
       orf.fac

   -- returns minimum index
   minIndex(orf : %) : NNI ==
       m : NNI := 0
       inds : List(NNI) := getIndexes(orf)
       for i in inds repeat
           if m = 0 or i < m then m := i
       m

   -- returns maximum index
   maxIndex(orf : %) : NNI ==
       m : NNI := 0
       inds : List(NNI) := getIndexes(orf)
       for i in inds repeat
           if i > m then m := i
       m

   -- returns indexes in face with sign to give orientation
   getIndexesSigned(orf : %) : List(Integer) ==
       res := []$List(Integer)
       neg : Boolean := (sign(orf.mul) = -1)
       for i in orf.fac repeat
           i2 : Integer := if neg then (-i)::Integer else i::Integer
           res := concat(res, i2)
           neg := not neg
       res

   -- returns multiplier of face
   getMult(orf : %) : Integer ==
       orf.mul

   -- sets multiplier of face
   setMult!(orf : %, m : Integer) : % ==
       orf.mul := m
       orf

   -- construct the boundary. This is like hollowing out the facet.
   boundary(orf : %) : List(%) ==
       facet : List(NNI) := orf.fac
       -- reverse outer list to get in numeric order
       revFacet : List(NNI) := reverse(facet)
       mult : Integer := orf.mul
       res := []$List(%)
       even : Boolean := false
       for x in revFacet repeat
           r : List(NNI) := remove(x, facet)
           m : Integer := if even then -mult else mult
           even := not even
           r1 : % := orientedFacet(m, r)
           res := concat(res, r1)$List(%)
       res

   -- local function to add if new
   addIfNew(lst : List(%), orf : %) : List(%) ==
       -- print("addIfNew(" << lst << ", " << orf <<
       --       ")")
       if not empty?(orf) and member?(orf, lst)$List(%) then return lst
       concat(lst, orf)

   -- local function to add list if new
   addIfNew2(lst : List(%), orfs : List(%)) : List(%) ==
       -- print("addIfNew2(" << lst << ", " << orfs <<
       --       ")")
       if empty?(orfs) then return lst
       res : List(%) := copy(lst)
       for orf in orfs repeat
           if not empty?(orf) and not member?(orf, res)$List(%) then
               res := concat(res, orf)
       --print("addIfNew2(" << lst << ", " << orfs << _
       --    ") res=" << res)
       res

   -- allSubsets of given facet
   -- returned in reverse order
   -- subsets returned are not oriented
   allSubsets(orf : %, minDim : NNI) : List(%) ==
       -- print("allSubsets in (" << orf << ", " <<
       --       minDim << ")")
       facet : List(NNI) := orf.fac
       res := []$List(%)
       if order(orf) <= minDim then
           -- print("allSubsets out 1 orf" << orf <<
           --       " order(orf)=" << order(orf) <<
           --         " result=" << res)
           return res
       for x in facet repeat
           -- print("allSubsets 2 x=" << x << " in facet="
           --       << facet)
           r : List(NNI) := remove(x, facet)
           r1 : % := orientedFacet(1, r)
           res := addIfNew(res, r1)
           -- print("allSubsets 3 order(r1)=" << order(r1) <<
           --       " minDim=" << minDim)
           if order(r1) > minDim then
               sub : List(%) := allSubsets(r1, minDim)
               if not empty?(sub) then res := addIfNew2(res, sub)
       -- print("allSubsets out orf=" << orf << " result="
       --       << res)
       res

   -- allSubsets of a given length
   -- returned in reverse order
   -- subsets returned are not oriented
   allSubsets(orf : %, minDim : NNI, maxDim : NNI) : List(%) ==
       --print("allSubsets(" << orf << ", " << _
       --       minDim << ", " << maxDim << ")")
       res : List(%) := allSubsets(orf, minDim)
       --print("allSubsets res=" << res)
       res2 := []$List(%)
       for y in res repeat
           if order(y) <= maxDim then
               res2 := concat(res2, y)$List(%)
       --print("allSubsets result=" << res2)
       res2

   -- local function, used by link, which returns true if face 'a' is
   -- contained in face 'b'.
   -- true subset only : returns false if a=b
   isSubsetOf?(sa : %, sb : %) : Boolean ==
       all : List(%) := allSubsets(sb, 1)
       for vertex in all repeat
           if vertex=sa then return true
       -- print("isSubsetOf?(" << sa << ", " << sb <<
       --       ")=true")
       false

   -- used by simplicialJoin. Returns join (union) of 'a' and 'b'.
   -- That is: returns a facet containing indexes in both 'a' and 'b'.
   join(sa : %, sb : %) : % ==
       lst : List(NNI) := concat(sa.fac, sb.fac)$List(NNI)
       lst2 := sort(removeDuplicates(lst))
       mult : Integer := sa.mul * sb.mul
       [mult, lst2]

   -- used by link. Returns face 'a' minus the vertices in face 'b'.
   subtract(sa : %, sb : %) : % ==
       --print("subtract(" << sa << ", " << _
       --       sb << ")=" << setDifference(sb, sa)$SIMPLEX)
       lst : List(NNI) := setDifference(sb.fac, sa.fac)$List(NNI)
       [1, lst]

   -- re-index so that indexes start at 1 and are then consecutive.
   reindex(sa : %) : % ==
       facet : List(NNI) := sa.fac
       -- first calculate the mapping
       oldval : List(NNI) := []
       newval : List(NNI) := []
       i : NNI := 1
       for v in facet repeat
           oldval := concat(oldval, v)
           newval := concat(newval, i)
           i := i + 1
       -- now apply the mapping
       res : List(NNI) := []
       for v in facet repeat
           ptr : Integer := position(v, oldval)
           if ptr < 1 then error "error in reindex"
           --print("reindex old val=" << qelt(oldval, ptr) <<_
           --        " new val=" << qelt(newval, ptr))
           res := concat(res, qelt(newval, ptr))
       [1, res]

   -- boolean word permutation
   -- find all permutations of numZeros and numOnes
   -- used by product to calculate all paths through lattice
   boolWordPermute(numZeros : NNI, numOnes : NNI) : List(List(Boolean)) ==
       boolWordPermute([]$List(List(Boolean)), numZeros, numOnes)

   -- recursive part of boolWordPermute
   boolWordPermute(wordIn : List(List(Boolean)), numZeros : NNI,
                   numOnes : NNI) : List(List(Boolean)) ==
       -- print("boolWordPermute(" << numZeros << ", "
       --        << numOnes << ")")
       if numZeros<1 and numOnes<1 then
           --print("boolWordPermute wordIn=" << wordIn)
           return wordIn
       res := []$List(List(Boolean))
       if numZeros > 0 then
           z : NNI := subtractIfCan(numZeros, 1)::NNI
           word0 := []$List(List(Boolean))
           if empty?(wordIn) then word0 := [[false]]
           for x in wordIn repeat
               subWord : List(Boolean) := concat(x, false)
               word0 := concat(word0, subWord)
           res := concat(res, boolWordPermute(word0, z, numOnes))
       if numOnes > 0 then
           z : NNI := subtractIfCan(numOnes, 1)::NNI
           word1 := []$List(List(Boolean))
           if empty?(wordIn) then word1 := [[true]]
           for x in wordIn repeat
               subWord : List(Boolean) := concat(x, true)
               word1 := concat(word1, subWord)
           res := concat(res, boolWordPermute(word1, numZeros, z))
       --print("boolWordPermute res=" << res)
       res

   -- this function returns the product of two facets.
   -- For more detailed explanation of simplex product code see:
   -- \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/product/}
   -- Also see Hatcher [9].
   -- page 277 - The simplicial cross product.
   -- page 278 - The cohomology cross product.
   -- Returns list, each entry is a 'lattice path' from (0, 0) to (x, y)
   -- where x is number of entries in sa and y is number of entries in sb.
   -- Calculation of 'lattice paths' is a combinatorics problem so perhaps
   -- it should really be done by combinatorics code?
   product(a : %, b : %) : List(ProductFacet) ==
       res := []$List(ProductFacet)
       indA : List(NNI) := getIndexes(a)
       indB : List(NNI) := getIndexes(b)
       lenA : NNI := subtractIfCan(#indA, 1)::NNI
       lenB : NNI := subtractIfCan(#indB, 1)::NNI
       perms : List(List(Boolean)) := reverse(boolWordPermute(lenA, lenB))
       --print("product perms=" << perms)
       for perm in perms repeat
           leftIndex : NNI := 1
           rightIndex : NNI := 1
           i : Record(left : NNI, right : NNI) :=
                                 [indA.leftIndex, indB.rightIndex]
           i2 : List(Record(left : NNI, right : NNI)) := [i]
           for b in perm repeat
               if b then
                   rightIndex := rightIndex + 1
               else
                   leftIndex := leftIndex + 1
               if leftIndex > #indA then error "leftIndex too high in product"
               if rightIndex > #indB then
                   error "rightIndex too high in product"
               i := [indA.leftIndex, indB.rightIndex]
               i2 := concat(i2, i)
           pf := productFacet(1, i2)
           res := concat(res, pf)
       res

   -- return index of e in lst. Index will be negative if e has
   -- negative multiplier.
   position(e : %, lst : List(%)) : Integer ==
       res : Integer := 1
       for x in lst repeat
           if e.fac = x.fac then
               if e.mul=x.mul then return res
               return -res
           res := res + 1
       return 0

   -- used by cone. Adds a vertex to face.
   addVertex(sa : %, v : NNI) : % ==
       f : List(NNI) := sa.fac
       f := concat(f, v)
       orientedFacet(1, f)

   -- adds 'n' to each index. Used to refactor simplexes so that
   -- they don't overlap.
   refactorIndexes(a : %, n : NNI) : % ==
       facet : List(NNI) := a.fac
       newval : List(NNI) := []
       for v in facet repeat
           newval := concat(newval,v+n)
       [a.mul, newval]

   -- true if this is the same face although the orientation
   -- may be different
   sameFace?(a : %, b : %) : Boolean ==
       vertsa : List(NNI) := a.fac
       vertsb : List(NNI) := b.fac
       not(vertsa ~= vertsb)

   -- less than Local
   -- calls itself recursively
   ltLocal(a : List(NNI), b : List(NNI)) : Boolean ==
       if a = b then return false
       -- if lists are different lengths then shortest first
       if ((#a) < 1) then return true
       if ((#b) < 1) then return false
       a1 : NNI := a.first
       b1 : NNI := b.first
       if a1 < b1 then return true
       if a1 > b1 then return false
       -- if lists are different lengths then shortest first
       if ((#a) < 2) then return true
       if ((#b) < 2) then return false
       ltLocal(a.rest, b.rest)

   -- less than
   -- define an order (OrderedSet category) so we can sort lists.
   ((ain : %) < (bin : %)) : Boolean ==
       --print("<(" << ain << ", " << bin << ")")
       a : List(NNI) := ain.fac
       b : List(NNI) := bin.fac
       ltLocal(a, b)

   -- less than or equal
   -- define an order so we can sort lists
   ((ain : %) <= (bin : %)) : Boolean ==
       --print("<=(" << ain << ", " << bin << ")")
       a : List(NNI) := ain.fac
       b : List(NNI) := bin.fac
       if a = b then return true
       ltLocal(a, b)

   -- Returns true if x equals y.
   -- Assumes indexes are in order as this is forced by constructor.
   _=(a : %, b : %) : Boolean ==
       if a.mul ~= b.mul then return false
       a.fac = b.fac

   -- output
   coerce(s : %) : OutputForm ==
       prefix := message("")
       m : Integer := s.mul
       if abs(m) ~= 1 then
           prefix := hconcat(m::OutputForm, message("*"))
       if m = -1 then prefix := message("-")
       elements : List(OutputForm) := [x::OutputForm for x in s.fac]
       --print("orientedFacet coerce elements=" << _
       --   elements << " m=" << m)
       body : OutputForm := paren(elements)
       hconcat(prefix, body)

)if false
\section{Product Facet}
This is intended to hold the product of two facets.

For more information see:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/cubical/}

The product has two indexes to represent each vertex, that is
the Cartesian product of the vertices of the operands. Usually
this double vertex will be normalised back to a single set of indexes
but this can only be done after the whole simplicial complex has been
multiplied so the corresponding facets can be matched up and sorted.
So the representation is:
Rep := Record(mul : Integer, left : List(NNI), right : List(NNI))

)endif

)abbrev domain PFACET ProductFacet
++ Author: Martin Baker
++ Description:
++   Holds the product of two simplexes
++   for more documentation see:
++   http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/product/
++ Date Created: April 2016
++ Basic Operations:
++ Related packages:
++ Related categories: FacetCategory
++ Related Domains:
++ Also See:
++ AMS Classifications:
++ Keywords:
++ Examples:
++ References:

ProductFacet() : Exports == Impl where
  NNI==> NonNegativeInteger
  x<<y ==> hconcat(x::OutputForm, y::OutputForm)

  Exports ==> FacetCategory with
    productFacet : (mul : Integer,
                    ind1 : List(Record(left : NNI, right : NNI))) -> %
      ++ Constructor for product facet
    productFacet : (mul : Integer, other : ProductFacet) -> %
      ++ Copy constructor to allow change of sign
    getIndexs : (orf : %) -> List(Record(left : NNI, right : NNI))
      ++ returns left indexes
    getMaxLeft : (orf : %) -> NNI
      ++ returns maximum left index
    getMaxRight : (orf : %) -> NNI
      ++ returns maximum right index
    setMult! : (orf : %, m : Integer) -> %
      ++ sets multiplier of face
    addVertex2 : (sa : %, v : Record(left : NNI, right : NNI))-> %
      ++ used by cone. Adds a vertex to face.

  Impl ==> add

   -- A facet is a list of indexes (ordered)
   -- 'mul' encodes the orientation. When we are interpreting this as
   -- a geometric object it is usually 1 or -1 (to reverse direction).
   -- When we are interpreting this as a linear algebra object then we
   -- treat it as an integer.
   Rep := Record(mul : Integer, ind : List(Record(left : NNI, right : NNI)))

   -- Constructor for product facet
   productFacet(mul1 : Integer, ind1 : List(Record(left : NNI, right : NNI))
               ) : % ==
       [mul1, ind1]

   -- Copy constructor to allow change of sign
   productFacet(mul1 : Integer, other : ProductFacet) : % ==
       [mul1, getIndexs(other)]

   -- returns left and right indexes
   getIndexs(orf : %) : List(Record(left : NNI, right : NNI)) ==
       orf.ind

   -- returns maximum left index
   getMaxLeft(orf : %) : NNI ==
       m : NNI := 0
       indexes : List(Record(left : NNI, right : NNI)) := getIndexs(orf)
       for index in indexes repeat
           if index.left > m then m := index.left
       m

   -- returns maximum right index
   getMaxRight(orf : %) : NNI ==
       m : NNI := 0
       indexes : List(Record(left : NNI, right : NNI)) := getIndexs(orf)
       for index in indexes repeat
           if index.right > m then m := index.right
       m

   -- number of vertices
   order(f : %) : NNI ==
       #getIndexs(f)

   -- returns multiplier of face
   getMult(orf : %) : Integer ==
       orf.mul

   -- sets multiplier of face
   setMult!(orf : %, m : Integer) : % ==
       orf.mul := m
       orf

   -- used by cone. Adds a vertex to face.
   addVertex2(sa : %, v : Record(left : NNI, right : NNI)) : % ==
       f : List(Record(left : NNI, right : NNI)) := getIndexs(sa)
       f := concat(f, v)
       productFacet(1, f)

   -- Returns true if x equals y.
   -- Assumes indexes are in order as this is forced by constructor.
   _=(a : %, b : %) : Boolean ==
       if a.mul ~= b.mul then return false
       indas : List(Record(left : NNI, right : NNI)) := getIndexs(a)
       indbs : List(Record(left : NNI, right : NNI)) := getIndexs(b)
       if #indas ~= #indbs then return false
       for inda in indas for indb in indbs repeat
           if inda.left ~= indb.left then return false
           if inda.right ~= indb.right then return false
       true

   -- output
   coerce(s : %) : OutputForm ==
       prefix := message("")
       m : Integer := s.mul
       if abs(m) ~= 1 then
           prefix := hconcat(m::OutputForm, message("*"))
       if m = -1 then prefix := message("-")
       elements := []$List(OutputForm)
       for x in s.ind repeat
           l : NNI := x.left
           r : NNI := x.right
           element := bracket([l::OutputForm, r::OutputForm])
           elements := concat(elements, element)
       --print("orientedFacet coerce elements=" << _
       --   elements << " m=" << m)
       body : OutputForm := paren(elements)
       hconcat(prefix, body)

)if false
\section{Cubical Facet}
As an alternative to simplicial complexes we can base our topology on
squares rather than triangles.

For more information see:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/cubical/}

However we need to implement this in a different way, if we specified
a square by its vertices then we could not be sure that the vertices
were all in the same plane and we could not be sure that the squares
aligned.

We therefore have separate indexes for each dimension and we specify
these as an interval.
)endif

)abbrev domain CFACET CubicalFacet
++ Author: Martin Baker
++ Description:
++   A single vertex, edge, square, cube...
++   for more documentation see:
++   http://www.euclideanspace.com/prog/scratchpad/mycode/topology/cubical/
++ Date Created: April 2016
++ Basic Operations:
++ Related packages:
++ Related categories: FacetCategory
++ Related Domains:
++ Also See:
++ AMS Classifications:
++ Keywords:
++ Examples:
++ References:

CubicalFacet() : Exports == Impl where
  NNI==> NonNegativeInteger
  x<<y ==> hconcat(x::OutputForm, y::OutputForm)

  Exports ==> FacetCategory with
    cubicalFacet : (mul : Integer, fac : List(Segment(Integer))) -> %
      ++ Constructor for cubical facet
      ++ intervals are always stored in low..high order
      ++ orientation is calculated by the number of intervals that have to be
      ++ flipped to put it in order.
    cubicalFacet : (mul : Integer, fac : List(List(Integer))) -> %
      ++ Constructor for cubical facet
    cubicalFacet : (mul : Integer, fac : CubicalFacet) -> %
      ++ Copy constructor which can change mul
    getIntervals : (orf : %) -> List(Segment(Integer))
      ++ returns intervals in face one for each dimension
    setMult : (orf : %, m : Integer) -> %
      ++ sets multiplier of face
    position : (e : %, lst : List(%)) -> Integer
      ++ return index of e in lst. Index will be negative if e has
      ++ negative multiplier.
    product : (a : %, b : %) -> %
      ++ This function returns the product of two cubical facets.
      ++ Unlike the simplex case, the product is a single facet
    boundary : (orf : %) -> List(%)
      ++ construct the boundary. This is like hollowing out
      ++ the facet.
    sameFace? : (a : %, b : %) -> Boolean
      ++ true if this is the same face although the orientation
      ++ may be different.
    allSubsets : (orf : %, minDim : NNI) -> List(%)
      ++ allSubsets of a given facet
      ++ returned in reverse order
      ++ subsets returned are not oriented
    allSubsets : (orf : %, minDim : NNI, maxDim : NNI) -> List(%)
      ++ allSubsets of a given length
      ++ returned in reverse order
      ++ subsets returned are not oriented

  Impl ==> add

   -- A facet is a list of indexes (ordered)
   -- 'mul' encodes the orientation. When we are interpreting this as a
   -- geometric object it is usually 1 or -1 (to reverse direction).
   -- When we are interpreting this as a linear algebra object then we
   -- treat it as an integer.
   Rep := Record(mul : Integer, fac : List(Segment(Integer)))

   -- Constructor for cubical facet
   -- intervals are always stored in low..high order
   -- orientation is calculated by the number of intervals that have
   -- to be flipped to put it in order.
   cubicalFacet(mul1 : Integer, fac1 : List(Segment(Integer))) : % ==
       --print("cubicalFacet(" << mul1 << ", " << fac1)
       mul2 : Integer := mul1
       fac2 := []$List(Segment(Integer))
       invert : Boolean := false
       for x in fac1 repeat
           interv : Segment(Integer) := x
           lo := low(x)
           hi := high(x)
           if lo > hi then
               interv := segment(hi, lo)
               invert := not invert
           fac2 := concat(fac2, interv)
       if invert then mul2 := -mul2
       [mul2, fac2]

   -- Constructor for cubical facet
   cubicalFacet(mul1 : Integer, fac1 : List(List(Integer))) : % ==
       --print("cubicalFacet(" << mul1 << ", " << fac1)
       fac2 := []$List(Segment(Integer))
       for x in fac1 repeat
           if #x ~= 2 then error "inner list should contain low, high"
           interv : Segment(Integer) := segment(first(x), second(x))
           fac2 := concat(fac2, interv)
       [mul1, fac2]

   -- Copy constructor which can change mul
   cubicalFacet(mul1 : Integer, fac1 : CubicalFacet) : % ==
       fac2 : List(Segment(Integer)) := getIntervals(fac1)
       [mul1, fac2]

   -- returns intervals in face one for each dimension
   getIntervals(orf : %) : List(Segment(Integer)) ==
       orf.fac

   -- The number of non-degenerate intervals.
   -- In simplex this would be the number of vertices but here it is
   -- more like the number of degrees of freedom.
   order(f : %) : NNI ==
       value : NNI := 0
       facet : List(Segment(Integer)) := f.fac
       for seg in facet repeat
           lo := low(seg)
           hi := high(seg)
           value := value + abs(hi-lo)::NNI
       value

   -- returns multiplier of face
   getMult(orf : %) : Integer ==
       orf.mul

   -- sets multiplier of face
   setMult(orf : %, m : Integer) : % ==
       fac2 : List(Segment(Integer)) := getIntervals(orf)
       cubicalFacet(m, fac2)

   -- return index of e in lst. Index will be negative if e has
   -- negative multiplier.
   position(e : %, lst : List(%)) : Integer ==
       --print("position(" << e << ", " << lst << ")")
       res : Integer := 1
       for x in lst repeat
           if e.fac = x.fac then
               if e.mul=x.mul then return res
               return -res
           res := res + 1
       return 0

   -- This function returns the product of two cubical facets.
   -- Unlike the simplex case, the product is a single facet
   -- For more detailed explanation of cubical product code see:
   -- \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/cubical/}
   product(a : %, b : %) : % ==
       aint : List(Segment(Integer)) := getIntervals(a)
       bint : List(Segment(Integer)) := getIntervals(b)
       cubicalFacet(1, concat(aint, bint))

   -- local function used by boundary operator.
   boundaryOneDimension(orf : %, dim : NNI, invert : Boolean) : List(%) ==
       facet : List(Segment(Integer)) := orf.fac
       mult : Integer := orf.mul
       subFacet1 := []$List(Segment(Integer))
       subFacet2 := []$List(Segment(Integer))
       for seg in facet for segn in 1..(#facet) repeat
           lo := low(seg)
           hi := high(seg)
           if segn = dim then
               subFacet1 := concat(subFacet1, segment(lo, lo))
               subFacet2 := concat(subFacet2, segment(hi, hi))
           else
               subFacet1 := concat(subFacet1, seg)
               subFacet2 := concat(subFacet2, seg)
       if invert then mult := -mult
       res := []$List(%)
       r1 : % := cubicalFacet(-mult, subFacet1)
       res := concat(res, r1)$List(%)
       r2 : % := cubicalFacet(mult, subFacet2)
       res := concat(res, r2)$List(%)
       res

   -- Boundary operator. This is like hollowing out the facet.
   boundary(orf : %) : List(%) ==
       facet : List(Segment(Integer)) := orf.fac
       -- dims will hold array of indexes of non-degenerate intervals
       dims := []$List(NNI)
       for seg in facet for segn in 1..(#facet) repeat
           lo := low(seg)
           hi := high(seg)
           if (lo ~= hi) then dims := concat(dims, segn)
       res := []$List(%)
       invert : Boolean := false
       -- for each non-degenerate interval add boundary
       for dim in dims repeat
           -- print("boundary(" << orf << ", " << dim <<
           --       ")")
           res := concat(res, boundaryOneDimension(orf, dim, invert))
           invert := not invert
       --print("boundary res=" << res)
       res

   -- local function to add if new
   addIfNew(lst : List(%), orf : %) : List(%) ==
       -- print("addIfNew(" << lst << ", " << orf <<
       --       ")")
       if not empty?(orf) and member?(orf, lst)$List(%) then return lst
       concat(lst, orf)

   -- local function to add list if new
   addIfNew2(lst : List(%), orfs : List(%)) : List(%) ==
       -- print("addIfNew2(" << lst << ", " << orfs <<
       --       ")")
       if empty?(orfs) then return lst
       res : List(%) := copy(lst)
       for orf in orfs repeat
           if not empty?(orf) and not member?(orf, res)$List(%) then
               res := concat(res, orf)
       --print("addIfNew2(" << lst << ", " << orfs << _
       --    ") res=" << res)
       res

   -- allSubsets of given facet
   -- returned in reverse order
   -- subsets returned are not oriented
   allSubsets(orf : %, minDim : NNI) : List(%) ==
       -- print("allSubsets in (" << orf << ", " << minDim
       --       << ")")
       facet : List(Segment(Integer)) := orf.fac
       res := []$List(%)
       if order(orf) <= minDim then
           -- print("allSubsets out 1 orf=" << orf <<
           --       " order(orf)=" << _
           --         order(orf) << " result=" << res)
           return res
       for x in facet for xn in 1..(#facet) repeat
           -- print("allSubsets 2 x=" << x << " in facet="
           --       << facet)
           lo := low(x)
           hi := high(x)
           if lo ~= hi then
               segLo : Segment(Integer) := segment(lo, lo)
               segHi : Segment(Integer) := segment(hi, hi)
               rLo : List(Segment(Integer)) := copy(facet)
               setelt!(rLo, xn, segLo)
               rLo1 : % := cubicalFacet(1, rLo)
               res := addIfNew(res, rLo1)
               rHi : List(Segment(Integer)) := copy(facet)
               setelt!(rHi, xn, segHi)
               rHi1 : % := cubicalFacet(1, rHi)
               res := addIfNew(res, rHi1)
               -- print("allSubsets 3 order(rLo1)=" << order(rLo1)
               --       << " minDim=" << minDim)
               if order(rLo1) > minDim then
                   sub : List(%) := allSubsets(rLo1, minDim)
                   if not empty?(sub) then res := addIfNew2(res, sub)
               if order(rHi1) > minDim then
                   sub : List(%) := allSubsets(rHi1, minDim)
                   if not empty?(sub) then res := addIfNew2(res, sub)
       -- print("allSubsets out orf=" << orf << " result=" << res)
       res

   -- allSubsets of a given length
   -- returned in reverse order
   -- subsets returned are not oriented
   allSubsets(orf : %, minDim : NNI, maxDim : NNI) : List(%) ==
       --print("allSubsets(" << orf << ", " << _
       --       minDim << ", " << maxDim << ")")
       res : List(%) := allSubsets(orf, minDim)
       --print("allSubsets res=" << res)
       res2 := []$List(%)
       for y in res repeat
           if order(y) <= maxDim then
               res2 := concat(res2, y)$List(%)
       --print("allSubsets result=" << res2)
       res2

   -- true if this is the same face although the orientation
   -- may be different
   sameFace?(a : %, b : %) : Boolean ==
       vertsa : List(Segment(Integer)) := a.fac
       vertsb : List(Segment(Integer)) := b.fac
       not(vertsa ~= vertsb)

   -- less than Local
   ltLocal(a : List(Segment(Integer)), b : List(Segment(Integer))) : Boolean ==
       if a = b then return false
       -- if lists are different lengths then shortest first
       if ((#a) < 1) then return true
       if ((#b) < 1) then return false
       for a1 in a for b1 in b repeat
           loa := low(a1)
           hia := high(a1)
           lob := low(b1)
           hib := high(b1)
           if loa < lob then return true
           if loa > lob then return false
           if hia < hib then return true
           if hia > hib then return false
       false

   -- less than
   -- define an order so we can sort lists
   ((ain : %) < (bin : %)) : Boolean ==
       --"<"(ain : %, bin : %) : Boolean ==
       --print("<(" << ain << ", " << bin << ")")
       a : List(Segment(Integer)) := ain.fac
       b : List(Segment(Integer)) := bin.fac
       ltLocal(a, b)

   -- less than or equal
   -- define an order so we can sort lists
   ((ain : %) <= (bin : %)) : Boolean ==
       --"<="(ain : %, bin : %) : Boolean ==
       --print("<=(" << ain << ", " << bin << ")")
       a : List(Segment(Integer)) := ain.fac
       b : List(Segment(Integer)) := bin.fac
       if a = b then return true
       ltLocal(a, b)

   -- Returns true if x equals y.
   -- Assumes indexes are in order as this is forced by constructor.
   _=(a : %, b : %) : Boolean ==
       if a.mul ~= b.mul then return false
       a.fac = b.fac

   -- output
   coerce(s : %) : OutputForm ==
       prefix := message("")
       m : Integer := s.mul
       if abs(m) ~= 1 then
           prefix := hconcat(m::OutputForm, message("*"))
       if m = -1 then prefix := message("-")
       elements : List(OutputForm) := [x::OutputForm for x in s.fac]
       --print("orientedFacet coerce elements=" << _
       --   elements << " m=" << m)
       body : OutputForm := paren(elements)
       hconcat(prefix, body)

)if false
\section{Chain Complex}

Following from simplicial complexes and delta complexes I now want to
add chain complexes and co-chain complexes which are an algebriation
of these complexes.

The ChainComplex domain would be contain a sequence free abelian groups:
C0, C1, C2 ... 0
Which are defined like this:

C0 = Zero dimensional chain
= free abelian group on vertices.
Elements of C0 are intergral linear combinations of vertices such as: 2x+7y+5z

C1 = One dimensional chain
= free abelian group on directed edges (1-cells).
Elements of C1 are intergral linear combinations of edges such as: 2xy+7xz+5yz
These don't necessarily need to be a cycle or even connected.

C2 = two dimensional chain
= free abelian group on directed triangles (2-cells).

At this stage we are mostly interested in the linear
algebra structure. This comes from the mappings between these free
abelian groups. So we also need (non-free) abelian groups as quotients
of free abelian group. So the chain complex holds a set of free abelian
groups (chains) and a set of group homomorphisms.

\subsection{Creating Chain Complexes}
Here are two ways to construct a chain complex:
\begin{itemize}
\item Usually it is easier to construct a simplicial or delta complex
first and then call chain function.
\item We could alternatively construct from a list of matrices.
\end{itemize}
Here we construct a simplicial complex then call chain function:
\begin{verbatim}
(1) -> b1 := sphereSolid(2)$SimplicialComplexFactory(Integer)

   (1)  points 1..3
          (1,2,3)
                         Type: FiniteSimplicialComplex(Integer)
(2) -> chain(b1)

                  + 1    1    0 + + 1 +
                  |             | |   |
   (2)  [0  0  0],|- 1   0    1 |,|- 1|,[]
                  |             | |   |
                  + 0   - 1  - 1+ + 1 +
                                        Type: ChainComplex
\end{verbatim}
Or we could use constructor from a list of matrices.
\begin{verbatim}
chainComplex : (v : List(Matrix(Integer))) -> %
\end{verbatim}

\subsection{Representation of Chain Complexes}
This is a list of matrices although they represent (abelian) group
homomorphisms.

An alternative representation might be to use functions:
\begin{verbatim}
C0, C1, C2 ... 0
delta1:(C1) -> C0 , delta2:(C2) -> C1 ...
\end{verbatim}

where delta is a group homomorphism which respects the group structure:

\begin{verbatim}
delta(u+v) = delta u + delta v
delta(u^-1) =(delta u)^-1
\end{verbatim}

\subsection{Homology of Chain Complexes}
These group homomorphisms are represented by: Matrix(Integer)
so that we can solve using IntegerSmithNormalForm.

So the types of function that I would like to implement on
ChainComplex are:
\begin{verbatim}
image: (ChainComplex, delta) -> chain
kernel: (ChainComplex, delta) -> chain
\end{verbatim}

and we need to find cycles by finding null space of augmented matrix.

)endif

)abbrev domain CHAINC ChainComplex
++ Author: Martin Baker
++ Description:
++   Delta Complexes are defined by a sequence of 'face maps', These
++   can be represented by a list of matrices.
++   for more documentation see:
++   http://www.euclideanspace.com/prog/scratchpad/mycode/topology/chain/
++ Date Created: March 2016
++ Basic Operations:
++ Related packages:
++ Related categories:
++ Related Domains: CoChainComplex
++ Also See:
++ AMS Classifications:
++ Keywords:
++ Examples:
++ References:

ChainComplex() : Exports == Impl where
  NNI==> NonNegativeInteger
  x<<y ==> hconcat(x::OutputForm, y::OutputForm)

  Exports ==> SetCategory() with
    chainComplex : (v : List(Matrix(Integer))) -> %
      ++ constructor
    transition_matrices : % -> List(Matrix(Integer))
      ++ transition_matrices(a) gives list of transition matrices
      ++ of a.
    validate : (a : %) -> Boolean
      ++ true if this is a valid chain complex, that is:
      ++ 1. maps compose
      ++ 2. product of adjacent maps is zero
    homology : (a : %) -> List(Homology)
      ++ calculate homology using SmithNormalForm

  Impl ==> add

   -- Representation contains sequence of face maps in matrix
   -- form
   Rep := List(Matrix(Integer))

   -- constructor
   chainComplex(v : List(Matrix(Integer))) : % ==
       v

   transition_matrices(a : %) : List(Matrix(Integer)) == a::Rep

   -- true if this is a valid chain complex, that is:
   -- 1) maps compose
   -- 2) product of adjacent maps is zero
   validate(a : %) : Boolean ==
       len : NNI := #a
       if len < 2 then return true
       last : Matrix(Integer) := first(a)
       for m in rest(a::Rep) repeat
           if maxRowIndex(m) ~= maxColIndex(last) then
               print(message("validate failed nRows=") << maxRowIndex(m) <<
                   message(" not equal to nCols ") << maxColIndex(last))
               return false
           prod : Matrix(Integer) := last*m
           if not zero?(prod) then
               print(message(
                  "validate failed: product of adjacent maps should be zero")
                   << last << message(" * ") << m << message(" = ") << prod)
               return false
           last := m
       true

   -- calculate homology using SmithNormalForm
   homology(a : %) : List(Homology) ==
       --print("ChainComplex homology("  << a << ")")
       res := []$List(Homology)
       prev := empty()$Matrix(Integer)
       notFirst : Boolean := false
       for m1 in a repeat
           if notFirst then
               --print("chainComplex: " << nrows(m1) << "*" << ncols(m1) <<_
               --  " " << nrows(prev) << "*" << ncols(prev))
               m2 : Homology := homologyGroup(m1, prev)
               res := concat(res, m2)
           notFirst := true
           prev := m1
       res

   -- output
   coerce(s : %) : OutputForm ==
       lst : List(OutputForm) := [x::OutputForm for x in s]
       commaSeparate(lst)

)if false
\section{Cochain Complex}

For cochain we reverse the arrows. As with the chain we represent each
one of these mappings with a matrix. For a cochain this matrix is
transposed compared to the chain.

For examples see:
http://www.euclideanspace.com/prog/scratchpad/mycode/topology/chain/cochain/

In the example, on that page, we create a shape (triangle) with 3
points. We then associate a value (in this case an integer) to each
of these points:
\begin{tabular}{ll}
Point \ Value \\
1 \ 3 \\
2 \ 4 \\
3 \ 5 \\
\end{tabular}
\end{table}

The coboundary function then calculates the differences: [1,2,1]. That
is the difference between 3 and 4 is 1, the difference between 3 and 5
is 2, and the difference between 4 and 5 is 1.

When we apply the coboundary function again we get the difference
around the loop, that is, 0.

The values used here were Integers but we could use any type that
implements AbelianGroup.

For example we could have real (Float) values associated with each
point representing say voltage or height or some other quantity for
each point.

Perhaps the most obvious quantity we could associate with each point
is position, given by a vector relative to some origin and coordinate
system. However the type (domain) needs to implement AbelianGroup.
Unfortunately Vectors in FriCAS don't implement AbelianGroup because
it needs to implement 0:% which it can't because the nearest function
in Vector is zero: NonNegativeInteger -> % but this needs to know the
number of dimensions.

We therefore use DirectProduct which implements AbelianGroup.
There is also an example of this on the above web page.

For examples of cohomology see:
http://www.euclideanspace.com/prog/scratchpad/mycode/topology/homology/cohomol/
)endif

)abbrev domain COCHNC CoChainComplex
++ Author: Martin Baker
++ Description:
++   Delta Complexes are defined by a sequence of 'face maps', These can
++   be represented by a list of matrices.
++   for more documentation see:
++   http://www.euclideanspace.com/prog/scratchpad/mycode/topology/chain/cochain/
++ Date Created: Jan 2016
++ Basic Operations:
++ Related packages:
++ Related categories:
++ Related Domains: ChainComplex
++ Also See:
++ AMS Classifications:
++ Keywords:
++ Examples:
++ References:

CoChainComplex(VS : AbelianGroup) : Exports == Impl where
  NNI==> NonNegativeInteger
  x<<y ==> hconcat(x::OutputForm, y::OutputForm)

  Exports ==> SetCategory() with
    coChainComplex : (v : List(Matrix(Integer))) -> %
      ++ constructor
    coChainComplex : (s : ChainComplex) -> %
      ++ constructor from \spad{ChainComplex}
    validate : (a : %) -> Boolean
      ++ true if this is a valid chain complex, that is:
      ++ 1. maps compose
      ++ 2. product of adjacent coboundary maps is zero
    coboundary : (a : %,n : NNI,inp : List(VS)) -> List(VS)
      ++ calculate coboundary at dimension n for a given input
    coHomology : (a : %) -> List(Homology)
      ++ calculate homology using SmithNormalForm

  Impl ==> add

   -- Representation contains sequence of face maps in matrix
   -- form
   Rep := List(Matrix(Integer))

   -- constructor
   coChainComplex(v : List(Matrix(Integer))) : % ==
       v

   coChainComplex(s : ChainComplex) : % ==
       tm := transition_matrices(s)
       tm := reverse!(tm)
       map(transpose, tm)

   -- true if this is a valid cochain complex, that is:
   -- 1) maps compose
   -- 2) product of adjacent maps is zero
   validate(a : %) : Boolean ==
       len : NNI := #a
       if len < 2 then return true
       last : Matrix(Integer) := first(a)
       for m in rest(a::Rep) repeat
           if maxRowIndex(m) ~= maxColIndex(last) then
               print(message("validate failed nRows=") << maxRowIndex(m) <<
                   message(" not equal to nCols ") << maxColIndex(last))
               return false
           prod : Matrix(Integer) := last*m
           if not zero?(prod) then
               print(message(
                  "validate failed: product of adjacent maps should be zero")
                   << last << message(" * ") << m << message(" = ") << prod)
               return false
           last := m
       true

   -- calculate coboundary at dimension n for a given input
   coboundary(a : %,n : NNI,inp : List(VS)) : List(VS) ==
       --print(message "coboundary(" << a << message ", " << n << _
       --message ", " << inp << message ")")
       maps : List(Matrix(Integer)) := _
              reverse(a::Rep)
       res : List(VS) := []
       inpn : NNI := #inp
       for p in 1..inpn repeat
           base : List(Integer) := [(if x=p then 1 else 0) for x in 1..inpn]
           b : Vector(Integer) := vector(base)
           m : Matrix(Integer) := transpose(maps.n)
           vs : Vector(Integer) := b*m
           --print(message "b=" << b << message " m=" << m << _
           --message " vs=" << vs)
           res1 : List(VS) := []
           for v in entries(vs) repeat
               val : VS := inp.p
               --if even?(p) then val := -val
               if v=0 then res1 := concat(res1,0)
               if v>0 then res1 := concat(res1,-val)
               if v<0 then res1 := concat(res1,val)
               --print(message "v=" << v << message " val=" << val << _
               --      message " res1=" << res1 << message " res=" << res)
           if empty?(res)
               then res := res1
               else res := [(res.i + res1.i) for i in 1..(#res1)]
       res

   -- calculate homology using SmithNormalForm
   coHomology(a : %) : List(Homology) ==
       --print("CoChainComplex homology("  << a << ")")
       res := []$List(Homology)
       prev := empty()$Matrix(Integer)
       notFirst : Boolean := false
       for m1 in a repeat
           if notFirst then
               --print("coChainComplex: " << nrows(m1) << "*" << ncols(m1) <<_
               --  " " << nrows(prev) << "*" << ncols(prev))
               m2 : Homology := homologyGroup(m1, prev)
               res := concat(res, m2)
           notFirst := true
           prev := m1
       res

   -- output
   coerce(s : %) : OutputForm ==
       lst : List(OutputForm) := [x::OutputForm for x in s]
       commaSeparate(lst)

)if false

\section{FiniteSimplicialComplex}
\subsection{Creating Simplicial Complexes}

Here are 3 ways to construct a simplicial complex:
\begin{itemize}
\item Using SimplicialComplexFactory - This allows common complexes to
be easily created.
\item Using functions like product, star, link and join to construct
complexes from simpler cases.
\item Build from lists of indexes.
\end{itemize}
Here is an example of building from factory:

\begin{verbatim}
FACTORY:=SimplicialComplexFactory(Integer)

   (1)  SimplicialComplexFactory(Integer)
                           Type: Type

(2) -> band()$FACTORY

   (2)  points 1..6
          (1,2,3)
          (1,2,6)
          (1,5,6)
          (2,3,4)
          (3,4,5)
          (4,5,6)
          Type: FiniteSimplicialComplex(Integer)
\end{verbatim}
We can join two complexes like this:
\begin{verbatim}
(1) -> line := line()$SimplicialComplexFactory(Integer)

   (1)  points 1..2
           (1,2)
                   Type: FiniteSimplicialComplex(Integer)
(2) -> simplicialJoin(line,line,true)

   (2)  points 1..4
         (1,2,3,4)
                   Type: FiniteSimplicialComplex(Integer)
\end{verbatim}
Here is an example of building from lists of indexes.
\begin{verbatim}
(3) -> v1:List(List(NNI)) := [[1::NNI,2::NNI],[4::NNI]]

   (3)  [[1,2],[4]]
                     Type: List(List(NonNegativeInteger))
(4) -> sc1 := simplicialComplex([],4,v1)$ASIMP

   (4)  points 1..4
           (1,2)
            (4)
                   Type: FiniteSimplicialComplex(Integer)
\end{verbatim}
This example has 4 points:
\begin{itemize}
\item point 1 and 2 are endpoins of a line.
\item point 3 does not exist but we want to reserve the index.
\item point 4 exist but is not connected to anything.
\end{itemize}

\subsection{Representation of Simplicial Complexes}

The FiniteSimplicialComplex representation holds whole Simplicial
Complex. This consists of:
\begin{itemize}
\item A vertex set, which can be a list of points, or a list of any
AbelianGroup defined by the type parameter. This allows us to
define a 'geometric simplicial complex' or alternatively to hold
information used for cochain.
\item A NNI holding the total number of points. This allows us to
reserve unused points.
\item A list (representing set - no duplication, order unimportant) of
OrientedFacet. Each OrientedFacet defined in terms of points.
\end{itemize}

There is an alternative to FiniteSimplicialComplex which is
DeltaComplex and is discussed later.
)endif

)abbrev domain SIMPC FiniteSimplicialComplex
++ Author: Martin Baker
++ Description:
++   A Finite Simplicial Complex
++   for more documentation see:
++   http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/
++ Date Created: Dec 2015
++ Basic Operations: star, link, cone, product
++ Related packages:
++ Related categories:
++ Related Domains: DeltaComplex is an alternative representation
++    which is less compact but allows edges, triangles, etc to be indexed.
++ Also See:
++ AMS Classifications:
++ Keywords: topology, finite, simplicial, complex, edge, triangle
++ Examples:
++ References:

FiniteSimplicialComplex(VS : AbelianGroup) : Exports == Impl where
  NNI==> NonNegativeInteger
  x<<y ==> hconcat(x::OutputForm, y::OutputForm)

  Exports ==> SetCategory() with
    simplicialComplex : (v : List(VS), numPoints:NNI,
                         fs : List(List(NNI))) -> %
      ++ constructor where the simplices are supplied
    simplicialComplex : (v : List(VS), fs : List(List(NNI))) -> %
      ++ constructor where the simplices are supplied
    simplicialComplex : (v : List(VS)) -> %
      ++ construct empty simplex
    simplicialComplexIfCan : (dc : DeltaComplex(VS),trace : Boolean) -> _
                         Union(%, "failed")
      ++ construct FiniteSimplicialComplex from DeltaComplex with trace.
      ++ If there are 'implied' loops, such as facets with multiple
      ++ entries with the same index, then this function may fail.
    simplicialComplexIfCan : (dc : DeltaComplex(VS)) -> _
                         Union(%, "failed")
      ++ construct FiniteSimplicialComplex from DeltaComplex.
      ++ If there are 'implied' loops, such as facets with multiple
      ++ entries with the same index, then this function may fail.
    addSimplex : (a : %, f : OrientedFacet) -> %
      ++ Add a facet to this simplex
      ++ If the facet is also in the list then, do not append, instead
      ++ change the multiplier. Used by boundary function
    maxIndex : (s : %) -> NNI
      ++ find the maximum index.
    minIndex : (s : %) -> NNI
      ++ find the minimum index.
    grade : (s : %) -> List(List(OrientedFacet))
      ++ a function to grade simplexes according to their lengths.
      ++ This nests the lists according to their length.
      ++ Vertices, edges, triangles and so on.
      ++ The list is returned in reverse order, that is, it starts
      ++ at highest grade and works down.
    sort : (s : %) -> %
      ++ sort
    addImpliedFaces : (s : %) -> List(List(OrientedFacet))
      ++ a function to take a set of faces and add those that are implied
      ++ by the simplecicalComplex conventions (if they don't already
      ++ exist). For example, if the input is a triangle ((1, 2, 3))
      ++ then we would add:
      ++ its points ((1), (2), (3)).
      ++ and its edges ((1, 2), (1, 3), (2, 3)).
    boundary : (s : %) -> %
      ++ Step down to the next lower dimension, this is like hollowing
      ++ out the complex.
      ++ It takes the highest dimension entries and replaces them with
      ++ their boundaries, cancelling out where it can.
    star : (s : %, simplex : OrientedFacet) -> %
      ++ The 'star' of a simplicial complex and a facet contains only
      ++ the faces of s which include simplex.
    link : (s : %, simplex : OrientedFacet) -> %
      ++ The 'link' of a simplicial complex and a vertex contains the
      ++ boundary of the simplexes of s which include simplex.
    cone : (s : %, vertex : NNI) -> %
      ++ The 'cone' of a simplicial complex is formed by adding the
      ++ vertex to every facet.
      ++ 'vertex' should not already be included in existing faces,
      ++ this is not checked and so will lead to simplexes with
      ++ duplicate vertices.
    refactorIndexes : (a : %, n : NNI) -> %
      ++ add 'n' to all indexes without changing the structure of
      ++ this simplicial complex.
    makeDisjoint : (a : %, b : %) -> %
      ++ If index numbers of 'b' overlap with index numbers of 'a' then
      ++ refactor 'b' so that it does not overlap.
    simplicialJoin : (a : %, b : %, enforceDisjoint : Boolean) -> %
      ++ Forms join of two simplicial complexes.
      ++ The join S*T of two simplicial sets S and T is a new simplicial
      ++ set that may geometrically be thought of as a cone over T with
      ++ tip of shape S.
      ++ simplicialJoin is commutative and associative.
      ++ For more information:
      ++ \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/join/index.htm}
    merge : (a : %, b : %) -> %
      ++ Forms union of two simplicial complexes.
    product : (a : %, b : %) -> %
      ++ Forms product of two simplicial complexes.
      ++ For more detailed explanation of simplex product code see:
      ++ \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/product/}
      ++ Also see Hatcher [9].
      ++ page 277 - The simplicial cross product.
      ++ page 278 - The cohomology cross product.
      ++ Returns list, each entry is a 'lattice path' from (0, 0) to
      ++ (x, y) where x is number of entries in sa and y is number of
      ++ entries in sb.
    oneSkeleton : (s : %) -> DirectedGraph(NNI)
      ++ generates graph AKA 1-skeleton
    fundamentalGroup : (s : %) -> GroupPresentation
      ++ Generates fundamental group from this simplicial complex.
      ++ This works by converting to a DeltaComplex and then generating the
      ++ fundamentalGroup from that. This is not the most efficient method
      ++ because the conversion generates lots of higher order faceMaps
      ++ which are not used.
      ++ This will attempt to simplify the group presentation, if you
      ++ don't want this use alternative function of same name.
      -- TODO write a faster implementation which only generates edge and
      -- triangle faceMaps.
      ++ For more information:
      ++ \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/homotopy/index.htm}
    fundamentalGroup : (s : %, simplify : Boolean, trace : Boolean) -> GroupPresentation
      ++ Generates fundamental group from this simplicial complex.
      ++ This works by converting to a DeltaComplex and then generating the
      ++ fundamentalGroup from that. This is not the most efficient method
      ++ because the conversion generates lots of higher order faceMaps
      ++ which are not used.
      -- TODO write a faster implementation which only generates edge and
      -- triangle faceMaps.
      ++ This version has options to:
      ++ simplify - if true attempts to simplify the group presentation
      ++ trace - if true outputs trace of simplification rules used.
      ++ For more information:
      ++ \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/homotopy/index.htm}
    chain : (s : %) -> ChainComplex
      ++ returns a matrix sequence representing the face maps
      ++ in linear algebra form
      ++ For more information:
      ++ \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/chain/index.htm}
    coChain : (s : %) -> CoChainComplex(VS)
      ++ returns a matrix sequence representing the face maps
      ++ in linear algebra form
      ++ For more information:
      ++ \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/chain/cochain/index.htm}
    homology : (s : %) -> List(Homology)
      ++ calculate homology using SmithNormalForm
      ++ For more information:
      ++ \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/homology/index.htm}
    coHomology : (s : %) -> List(Homology)
      ++ calculate cohomology using SmithNormalForm
      ++ \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/homology/cohomol/index.htm}
    coerce : (s : %) -> DeltaComplex(VS)
      ++ coerce SimplicialComplex to DeltaComplex

  Impl ==> add

   -- Representation holds whole Simplicial Complex. This consists of
   -- a vertex set, here represented as a vertex list so that we can
   -- index it.
   -- Also a list of simplices (that is n-dimensional faces). each
   -- simplice is an array of vertex indexes.
   -- So each simplice is a subset of the vertex set.
   -- where:
   --  VS is a list of vertices in whatever form we are using.
   --  NNI is an index.
   --  The inner list represents an individual simplex.
   --  The outer list holds a list of simplexes.
   Rep := Record(VERTSET : List(VS),NUMPOINTS : NNI, SIMP : List(OrientedFacet))

   -- constructor where the vertex set and simplices are supplied
   simplicialComplex(v : List(VS), numPoints : NNI, fs : List(List(NNI))
                    ) : % ==
       --print("simplicialComplex(" << fs << ")")
       res := []$List(OrientedFacet)
       for f in fs repeat
           res := concat(res, orientedFacet(1, f))
       --print("simplicialComplex f=" << f << " res=" << res)
       [v, numPoints, res]

   -- constructor where the vertex set and simplices are supplied
   simplicialComplex(v : List(VS), fs : List(List(NNI))) : % ==
       facs := []$List(OrientedFacet)
       for f in fs repeat
           facs := concat(facs, orientedFacet(1, f))
       numPoints : NNI := 0
       for fac in facs repeat
           m := maxIndex(fac)
           if m>numPoints then numPoints := m
       [v,numPoints, facs]

   -- construct empty simplex
   simplicialComplex(v : List(VS)) : % ==
       res := []$List(OrientedFacet)
       [v,0, res]

   -- construct FiniteSimplicialComplex from DeltaComplex.
   -- For more information:
   -- \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/delta/delta2simplex/index.htm}
   simplicialComplexIfCan(dc : DeltaComplex(VS)) : Union(%, "failed") ==
       simplicialComplexIfCan(dc,false)

   -- construct FiniteSimplicialComplex from DeltaComplex with trace.
   -- For more information:
   -- \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/delta/delta2simplex/index.htm}
   simplicialComplexIfCan(dc : DeltaComplex(VS),trace : Boolean) : _
            Union(%, "failed") ==
       if trace then
           print(message("simplicialComplex(") << dc << message(")"))
       dcrep := dc pretend Record(VERTSET : List(VS), _
                                  MAPS : List(List(List(Integer))))
       NumPoints : NNI := 0
       vertset : List(VS) := dcrep.VERTSET
       maps : List(List(List(Integer))) := reverse(dcrep.MAPS)
       -- res contains list which will become the result
       res := []$List(OrientedFacet)
       -- deleteList contains list for removal at the end
       deleteList := []$List(OrientedFacet)
       lastMap := []$List(List(OrientedFacet))
       for map in maps for grade in 1..(#maps) repeat
           lastMap := concat(lastMap, []$List(OrientedFacet))
           if trace then
              print("simplicialComplex map=" << map << _
                             " grade=" << grade << _
                             " lastMap=" << lastMap)
           if grade = 1 then
               -- points so facets are equal to map
               NumPoints := #map
               thisPoint : NNI := 0
               for facet in map repeat
                   thisPoint := thisPoint + 1
                   if not empty?(facet) then
                       orf := orientedFacetSigned([thisPoint])
                       if trace then
                         print(message "simplicialComplex add point=" << _
                            orf <<  message " from face=" << facet)
                       res := concat(res, orf)
                       lastMap.grade :=concat(lastMap.grade, orf)
           if grade > 1 then
               -- for higher grades lookup index in next lower grade
               for facet in map repeat
                   --print(message "simplicialComplex g=" << grade <<
                   --      message" facet=" << facet)
                   subFacets := []$List(OrientedFacet)
                   for i in facet repeat
                       ind : NNI := abs(i)::NNI
                       subFacet : OrientedFacet := (lastMap.(grade-1)).ind
                       m : Integer := if i<1 then -1 else 1
                       -- construct an OrientedFacet with the appropriate
                       -- sign from index
                       subFacet2 : OrientedFacet := orientedFacet(m, subFacet)
                       deleteList := concat(deleteList, subFacet)
                       subFacets := concat (subFacets, subFacet2)
                   orfx : Union(OrientedFacet, "failed") := _
                          orientedFacetIfCan(subFacets)
                   if orfx case "failed" then return "failed"
                   orf := orfx@OrientedFacet
                   res := concat(res, orf)
                   lastMap.grade := concat(lastMap.grade, orf)
       -- remove any facets that are contained in higher facets
       for r in deleteList repeat
           res := remove(r, res)
       if trace then print(message("simplicialComplex construct res=") << res)
       [vertset,NumPoints, res]

   -- Add a facet to this simplex
   -- If the facet is also in the list then, do not append, instead
   -- change the multiplier.
   -- Used by boundary function
   addSimplex(a : %, f : OrientedFacet) : % ==
       res : List(OrientedFacet) := a.SIMP
       -- check for duplicates
       found : Boolean := false
       for orf in res repeat
           if getIndexes(orf)=getIndexes(f) then
               m : Integer := getMult(orf) + getMult(f)
               setMult!(orf, m)
               found := true
       if not found then
           res := concat(res, f)
       -- remove empty faces
       res2 := []$List(OrientedFacet)
       for orf in res repeat
           if getMult(orf) ~= 0 then
               res2 := concat(res2, orf)
       -- set number of points
       numPoints : NNI := 0
       for orf2 in res2 repeat
           max : NNI := maxIndex(orf2)
           if max > numPoints then numPoints := max
       [a.VERTSET, numPoints, res2]

   -- find the maximum index value.
   maxIndex(s : %) : NNI ==
       res : NNI := 0
       facs : List(OrientedFacet) := s.SIMP
       for fac in facs repeat
           m := maxIndex(fac)
           if m>res then res := m
       res

   -- find the minimum index value.
   minIndex(s : %) : NNI ==
       res : NNI := 10000
       facs : List(OrientedFacet) := s.SIMP
       if #facs = 0 then return 0
       for fac in facs repeat
           m := minIndex(fac)
           if m<res then res := m
       res

   -- A function to grade simplexes according to their lengths.
   -- This nests the lists according to their length.
   -- Vertices, edges, triangles and so on.
   -- The list is returned in reverse order, that is, it starts
   -- at highest grade and works down.
   grade(s : %) : List(List(OrientedFacet)) ==
       simp : List(OrientedFacet) := s.SIMP
       maxGrade : NNI := 0
       for face in simp repeat
           if order(face) > maxGrade then maxGrade := order(face)
       res := []$List(List(OrientedFacet))
       if maxGrade < 1 then return res
       for a in 1..maxGrade repeat
           res := concat(res, []$List(OrientedFacet))
       for face in simp repeat
           gr : Integer := order(face)
           if gr > 0 then
               res.gr := concat(res.gr, face)
       res

   -- sort faces to be in numeric order
   sort(s : %) : % ==
       v : List(VS) := s.VERTSET
       fs : List(OrientedFacet) := s.SIMP -- list of faces
       [v, s.NUMPOINTS, sort(fs)]

   -- a local function to return a list where each entry:
   -- i) is a permutation of the input list
   -- ii) contains integers in ascending order
   -- iii) is of the specified length
   -- TODO this is in both simplicial and delta complexes so need to find common
   -- home for it.
   orderedPermutation(lst : List(Integer), len :  NNI) : List(List(Integer)) ==
       if len < 1 then return []$List(List(Integer))
       if len > #lst then return []$List(List(Integer))
       if len = #lst then return [lst]
       reslt := []$List(List(Integer))
       if len=1 then
           for y in lst repeat
               reslt := concat(reslt, [y])
           return reslt
       fst : Integer := lst.first
       rst : List(Integer) := lst.rest
       -- len can never be negative due to the tests
       -- at the start of this function.
       for x in orderedPermutation(rst, subtractIfCan(len, 1)::NNI) repeat
           r1 : List(Integer) := concat(fst, x)
           -- r1 holds a permutations starting with fst
           reslt := concat(reslt, r1)
       -- now we have combinations staring with fst
       -- so we just have to add the rest
       reslt := concat(reslt, orderedPermutation(rst, len))
       reslt

   -- local function used in unorientedUnion in addImpliedFaces.
   -- Returns true if 'b' is an element of 'lst'.
   isNewFace?(lst : List(OrientedFacet), b : OrientedFacet) : Boolean ==
       --print("isNewFace?(" << a << ", " << b << ")")
       for a in lst repeat
           if sameFace?(a, b) then return false
       true

   -- local function used in addImpliedFaces.
   -- Merge two lists of facets. If a facet is contained in both then
   -- only include it once.
   unorientedUnion(a : List(OrientedFacet), b : List(OrientedFacet)
            ) : List(OrientedFacet) ==
       res := a
       for b1 in b repeat
           if isNewFace?(a, b1) then
               res := cons(b1, res)
       -- print("unorientedUnion(" << a << ", " << b << ") = " << res)
       res

   import from OutputForm

   -- A function to take a set of faces and add those that are implied
   -- by the simplicial complex conventions (if they don't already exist).
   -- For example, if the input is a triangle ((1, 2, 3)) then we would add
   -- its points ((1),(2),(3)) and its edges ((1, 2), (1, 3), (2, 3)).
   addImpliedFaces(s : %) : List(List(OrientedFacet)) ==
       inputList : List(List(OrientedFacet)) := reverse(grade(s))
       --print(message "addImpliedFaces s=" << s << _
       --      message " reverse=" << inputList)
       res := []$List(List(OrientedFacet))
       maxGrade : NNI := #inputList
       if (maxGrade) < 1 then
           print(message "addImpliedFaces return because empty" << maxGrade)
           return res
       -- create an empty list for each grade
       res := []$List(List(OrientedFacet))
       newFaces := []$List(OrientedFacet)
       for gr in inputList repeat
           --print("addImpliedFaces grade=" << gr)
           -- add old faces
           all_faces := unorientedUnion(newFaces, gr)
           --print(message"all_faces = " << all_faces::OutputForm)
           res := cons(all_faces, res)
           --print("addImpliedFaces res=" << res)
           newFaces := []
           --print("addImpliedFaces res.grn=" << res.grn)
           for face in all_faces repeat
               -- FIXME: handle order 1 too
               len := qcoerce(order(face) - 1)@NNI
               if len > 0 then
                   newFaces := unorientedUnion(newFaces,
                                               allSubsets(face, len, len))
           -- print(message("newFaces = "))
           -- print(newFaces::OutputForm)
           --print("addImpliedFaces get next grade res=" << res)
       --print("addImpliedFaces result=" << reverse(res))
       res

   -- Step down to the next lower dimension, this is like hollowing
   -- out the complex.
   -- It takes the highest dimension entries and replaces them with
   -- their boundaries, cancelling out where it can.
   boundary(s : %) : % ==
       res : % := simplicialComplex(s.VERTSET)
       --facets := []$List(OrientedFacet)
       for facet in s.SIMP repeat
           x : List(OrientedFacet) := boundary(facet)
           --print("boundary(" << s.SIMP << ") x=" << x)
           for y in x repeat
               --facets := concat(facets, y)$List(OrientedFacet)
               res := addSimplex(res, y)
       res

   -- The 'star' of a simplicial complex and a simplex contains only the faces
   -- of s which include simplex.
   star(s : %, simplex : OrientedFacet) : % ==
       res := []$List(OrientedFacet)
       for simp in s.SIMP repeat
           if isSubsetOf?(simplex, simp) then
               res := concat(res, simp)
       [s.VERTSET, s.NUMPOINTS, res]

   -- The 'link' of a simplicial complex and a simplex contains the
   -- boundary of the simplexes of s which include simplex.
   link(s : %, simplex : OrientedFacet) : % ==
       res := []$List(OrientedFacet)
       for facet in s.SIMP repeat
           if isSubsetOf?(simplex, facet) then
               x : List(OrientedFacet) := boundary(facet)
               res := concat(res, x)
       [s.VERTSET, s.NUMPOINTS, res]

   -- The 'cone' of a simplicial complex is formed by adding the vertex
   -- to every facet.
   -- 'vertex' should not already be included in existing faces, this
   -- is not checked and so will lead to simplexes with duplicate vertices.
   cone(s : %, vertex : NNI) : % ==
       res := []$List(OrientedFacet)
       for facet in s.SIMP repeat
           x : OrientedFacet := addVertex(facet, vertex)
           res := concat(res, x)
       [s.VERTSET, s.NUMPOINTS, res]

   -- add 'n' to all indexes without changing the structure of
   -- this simplicial complex.
   -- Used by makeDisjoint which is used by simplicialJoin.
   refactorIndexes(a : %, n : NNI) : % ==
       res := []$List(OrientedFacet)
       for facet in a.SIMP repeat
           r :OrientedFacet := refactorIndexes(facet,n)
           res := concat (res,r)
       [a.VERTSET, a.NUMPOINTS, res]

   -- If index numbers of 'b' overlap with index numbers of 'a' then
   -- refactor 'b' so that it does not overlap.
   -- Used by simplicialJoin.
   makeDisjoint(a : %, b : %) : % ==
       mina : NNI := minIndex(a)
       maxa : NNI := maxIndex(a)
       minb : NNI := minIndex(b)
       maxb : NNI := maxIndex(b)
       if maxa < minb then return b
       if maxb < mina then return b
       refactorIndexes(b,maxa)

   -- Forms join of two simplicial complexes.
   simplicialJoin(a : %, b : %, enforceDisjoint : Boolean) : % ==
       if empty?(a.SIMP) then return b
       if empty?(b.SIMP) then return a
       b2 : % := if enforceDisjoint then makeDisjoint(a, b) else b
       res := []$List(OrientedFacet)
       for x in a.SIMP repeat
           for y in b2.SIMP repeat
               --print ("simplicialJoin x=" << x << " y=" << y)
               fac : OrientedFacet := join(x,y)
               res := concat(res, fac)
               --print ("res=" << res)
       verts : List(VS) := concat(a.VERTSET,b.VERTSET)
       numPoints : NNI := a.NUMPOINTS + b.NUMPOINTS
       [verts, numPoints, res]

   -- Forms union of two simplicial complexes.
   merge(a : %, b : %) : % ==
       res : % := copy(a)
       for x in b.SIMP repeat
           res := addSimplex(res, x)
           -- print ("n=" << n << " offset=" << offset)
       -- print ("res=" << res)
       res

   -- local function used in oneSkeleton
   concatIfNew(a : List(Record(value : NNI, posX : NNI, posY : NNI)), _
               b : NNI) : _
               List(Record(value : NNI, posX : NNI, posY : NNI)) ==
       OBJT ==> Record(value : NNI, posX : NNI, posY : NNI)
       if member?([b, 0::NNI, 0::NNI], a)$List(OBJT) then return a
       concat(a, [b, 0::NNI, 0::NNI])$List(OBJT)

   -- local function used in oneSkeleton
   concatIfNewArrow(a : List(Record(name : String, arrType : NNI, _
                        fromOb : NNI, toOb : NNI, xOffset : Integer, _
                        yOffset : Integer, map : List NNI)), _
                   p : NNI, m : NNI):_
                   List(Record(name : String, arrType : NNI, fromOb : NNI, _
                        toOb : NNI, xOffset : Integer, yOffset : Integer, _
                        map : List NNI)) ==
       ARROW ==> Record(name : String, arrType : NNI, fromOb : NNI, _
                   toOb : NNI, xOffset : Integer, yOffset : Integer, _
                   map : List NNI)
       if member?(["x", 0::NNI, p, m, 0::Integer, 0::Integer, []],_
                a)$List(ARROW) then return a
       if member?(["x", 0::NNI, m, p, 0::Integer, 0::Integer, []], _
                a)$List(ARROW) then return a
       concat(a, ["x", 0::NNI, p, m, 0::Integer, 0::Integer, []])$List(ARROW)

   -- Generates graph AKA 1-skeleton.
   -- Used to generate fundamental group
   oneSkeleton(s : %) : DirectedGraph(NNI) ==
       ARROW ==> Record(name : String, arrType : NNI, fromOb : NNI, _
                   toOb : NNI, xOffset : Integer, yOffset : Integer, _
                   map : List NNI)
       OBJT ==> Record(value : NNI, posX : NNI, posY : NNI)
       GS := DirectedGraph(NNI)
       nodes := []$List(OBJT)
       edges := []$List(ARROW)
       for x in s.SIMP repeat
           -- SIMP is List(OrientedFacet) so x is OrientedFacet
           n : NNI := order(x)
           indexes : List(NNI) := getIndexes(x)
           if n=2 then
               nodes := concatIfNew(nodes, indexes.1)
               nodes := concatIfNew(nodes, indexes.2)
               y : ARROW := ["x", 0::NNI, indexes.1, indexes.2, 0::Integer, 0::Integer, []]
               edges := concat(edges, y)$List(ARROW)
               --print ("nodes=" << nodes << " edges=" << edges)
           if n > 2 then
               for m in indexes repeat
                   nodes := concatIfNew(nodes, m)
                   for p in indexes repeat
                       if p<m then
                           edges := concatIfNewArrow(edges, p::NNI, m::NNI)
       directedGraph(nodes, edges)$GS

   -- Generates fundamental group from this simplicial complex.
   -- This works by converting to a DeltaComplex and then generating the
   -- fundamentalGroup from that. This is not the most efficient method
   -- because the conversion generates lots of higher order faceMaps
   -- which are not used.
   -- This will attempt to simplify the group presentation, if you
   -- don't want this use alternative function of same name.
   -- TODO write a faster implementation which only generates edge and
   -- triangle faceMaps.
   fundamentalGroup(s : %) : GroupPresentation ==
       fundamentalGroup(s, true, false)

   -- Generates fundamental group from this simplicial complex.
   -- This works by converting to a DeltaComplex and then generating the
   -- fundamentalGroup from that. This is not the most efficient method
   -- because the conversion generates lots of higher order faceMaps
   -- which are not used.
   -- TODO write a faster implementation which only generates edge and
   -- triangle faceMaps.
   -- This version has options to:
   -- simplify - if true attempts to simplify the group presentation
   -- trace - if true outputs trace of simplification rules used.
   fundamentalGroup(s : %, simplify : Boolean, _
                    trace : Boolean) : GroupPresentation ==
       dc : DeltaComplex(VS) := deltaComplex(_
                    s pretend FiniteSimplicialComplex(VS))
       fundamentalGroup(dc, simplify, trace)

   -- returns a matrix sequence representing the face maps
   -- in linear algebra form
   chain(s : %) : ChainComplex ==
       dc : DeltaComplex(VS) := deltaComplex(_
                   s pretend FiniteSimplicialComplex(VS))
       chain(dc)$DeltaComplex(VS)

   -- returns a matrix sequence representing the face maps
   -- in linear algebra form
   coChain(s : %) : CoChainComplex(VS) ==
       coChainComplex(chain(s))

   -- calculate homology using SmithNormalForm
   homology(s : %) : List(Homology) ==
       dc : DeltaComplex(VS) := deltaComplex(_
               s pretend FiniteSimplicialComplex(VS))
       homology(dc)$DeltaComplex(VS)

   -- calculate homology using SmithNormalForm
   coHomology(s : %) : List(Homology) ==
       dc : DeltaComplex(VS) := deltaComplex(_
                   s pretend FiniteSimplicialComplex(VS))
       coHomology(dc)$DeltaComplex(VS)

   -- Forms product of two simplicial complexes.
   -- For more detailed explanation of simplex product code see:
   -- \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/simplex/product/}
   -- Also see Hatcher [9].
   -- page 277 - The simplicial cross product.
   -- page 278 - The cohomology cross product.
   -- Returns list, each entry is a 'lattice path' from (0, 0) to (x, y)
   -- where x is number of entries in sa and y is number of entries in sb.
   product(a : %, b : %) : % ==
       simpsa : List(OrientedFacet) := a.SIMP
       simpsb : List(OrientedFacet) := b.SIMP
       -- How do we represent the points? This should be the Cartesian
       -- product of the points in 'a' and the points in 'b', so this
       -- would increase the dimension of the points. But here
       -- we do not know how these points are represented, we can only
       -- do Abelian group operations (+, - or set to 0).
       -- The following is not correct but it is the best I can do for now.
       vs : List(VS) := []
       for pa in a.VERTSET repeat
           for pb in b.VERTSET repeat
               pp:VS := pa+pb
               vs := concat(vs,pp)
       res := []$List(ProductFacet)
       for simpa in simpsa repeat
           for simpb in simpsb repeat
               res := concat(res, product(simpa, simpb))
       --print("product(" << a << ", " << b << ") raw res=" << res)
       -- remove duplicates
       res2 := []$List(ProductFacet)
       for e in res repeat
           if not member?(e, res2) then res2 := concat(res2, e)
       --print("product(" << a << ", " << b << ") no dup=" << res2)
       -- normalise res2 so facets have one index list each
       res3 := []$List(OrientedFacet)
       dima : NNI := 0 -- maximum dimension of left element
       dimb : NNI := 0 -- maximum dimension of right element
       for z in res2 repeat
           az := getMaxLeft(z)
           if az > dima then dima := az
           bz := getMaxRight(z)
           if bz > dimb then dimb := bz
       for x in res2 repeat
           inds : List(Record(left : NNI, right : NNI)) := getIndexs(x)
           inds2 := []$List(NNI)
           mul : Integer := getMult(x)
           for y in inds repeat
               l : NNI := subtractIfCan(y.left, 1)::NNI
               r : NNI := subtractIfCan(y.right, 1)::NNI
               newIndex : NNI := l*dimb + r + 1
               inds2 := concat(inds2, newIndex)$List(NNI)
               --print("product indexes l=" << l << " r=" << r << " dima="
               --    << dima << " dimb=" << dimb << " newIndex=" << newIndex)
           res3 := concat(res3, orientedFacet(mul, inds2))
       numPoints : NNI := a.NUMPOINTS * b.NUMPOINTS
       [vs, numPoints, res3]

   -- Local function used by '=' to check if two simplexes are equal.
   -- To be equal they must contain the same indexes, wound in the same
   -- direction but not necessarily starting at the same value.
   equalSimplex(a : List(NNI), b : List(NNI)) : Boolean ==
       --print("equalSimplex:" << a << ", " << b)
       len : NNI := #a
       if len ~= #b then return false
       if len = 0 then return true
       fst : NNI := first a
       offset : Integer := position(fst, b)
       if offset < 1 then return false
       for n in 1..#a repeat
           if a.n ~= b.offset then return false
           offset := offset + 1
           if offset > #b then offset := 1
       true

   -- Returns true if x equals y.
   -- That is, they contain the same simplexes, although not necessarily
   -- in the same order.
   -- TODO - Assumes point set is exactly the same, for more generality
   -- we should allow the points to be defined in a different order.
   _=(a : %, b : %) : Boolean ==
       as : List(OrientedFacet) := a.SIMP
       bs : List(OrientedFacet) := b.SIMP
       --print("equal:" << as << ", " << bs)
       len : NNI := #as
       if len ~= #bs then return false
       -- flag for every entry in b
       flags : List(Boolean) := [false for x in as]
       --print("empty flags " << flags)
       for a1 in as repeat
           x : NNI := 1
           while (a1 ~= bs.x) or (flags.x) repeat
               --print("loop:" << x << " flags" << flags)
               x := x + 1
               if x > #as then return false
           flags.x := true
           --print("flags." << x << "=true " << flags)
       --print("final flags " << flags)
       for f in flags repeat
           if f = false then return false
       true

   -- output
   coerce(s : %) : OutputForm ==
       res : OutputForm := hconcat([message("points 1.."),
                                   (s.NUMPOINTS)::OutputForm])
       if empty?(s.SIMP) then return bracket(message("empty"))
       for a in s.SIMP repeat
           -- a is OrientedFacet
           res := vconcat(res, a::OutputForm)
       --print("FiniteSimplicialComplex coerce num=" << #(s.SIMP))
       res

   -- coerce SimplicialComplex to DeltaComplex
   coerce(s : %) : DeltaComplex(VS) ==
       deltaComplex(s pretend FiniteSimplicialComplex(VS))

)if false
\section{FiniteCubicalComplex}

Defines a set of n-dimensional facets.

This representation defines faces, of any dimension, by their intervals.
The representation is in two parts:
\begin{itemize}
\item The verities themselves which may either geometric (actual
  coordinates) or abstract.
\item A set of facets, each one represented by a set intervals.
\end{itemize}

)endif

)abbrev domain CUBEC FiniteCubicalComplex
++ Author: Martin Baker
++ Description:
++   A Finite Cubical Complex
++   for more documentation see:
++   http://www.euclideanspace.com/prog/scratchpad/mycode/topology/cubical/
++ Date Created: April 2016
++ Basic Operations:
++ Related packages:
++ Related categories:
++ Related Domains:
++ Also See:
++ AMS Classifications:
++ Keywords: topology, finite, cubical, complex, edge, triangle
++ Examples:
++ References:

FiniteCubicalComplex(VS : AbelianGroup) : Exports == Impl where
  NNI==> NonNegativeInteger
  x<<y ==> hconcat(x::OutputForm, y::OutputForm)

  Exports ==> SetCategory() with
    cubicalComplex : (v : List(VS), f : List(CubicalFacet)) -> %
      ++ constructor where the cubes are supplied
    cubicalComplex : (v : List(VS), f : List(List(Segment(Integer)))) -> %
      ++ constructor where the cubes are supplied
    cubicalComplex : (v : List(VS), f : List(List(List(Integer)))) -> %
      ++ constructor where the cubes are supplied
    cubicalComplex : (v : List(VS)) -> %
      ++ construct empty cubical complex
    maxIndex : (s : %) -> NNI
      ++ find the maximum index.
    addCube : (a : %, f : CubicalFacet) -> %
      ++ Add a facet to this simplex
      ++ If the facet is also in the list then, do not append, instead
      ++ change the multiplier.
    grade : (s : %) -> List(List(CubicalFacet))
      ++ A function to grade faces according to the number of non-degenerate
      ++ intervals. This nests the lists according to their order
      ++ Vertices, edges, squares and so on.
    addImpliedFaces : (s : %) -> List(List(CubicalFacet))
      ++ a function to take a set of faces and add those that are implied
      ++ by the cubicalComplex conventions (if they don't already exist).
      ++ For example, if the input is a square [1..2, 1..2] then we would add
      ++ its edges [1..1, 1..2], [2..2, 1..2], [1..2, 1..1], and [1..2, 2..2].
    product : (a : %, b : %) -> %
      ++ Forms product of two cubical complexes.
    fundamentalGroup : (s : %) -> GroupPresentation
      ++ Generates fundamental group from this cubical complex.
      ++ This works by converting to a DeltaComplex and then generating the
      ++ fundamentalGroup from that. This is not the most efficient method
      ++ because the conversion generates lots of higher order faceMaps
      ++ which are not used.
      ++ This will attempt to simplify the group presentation, if you
      ++ don't want this use alternative function of same name.
      ++ TODO write a faster implementation which only generates edge and
      ++ square faceMaps.
    fundamentalGroup : (s : %, simplify : Boolean, trace : Boolean) -> GroupPresentation
      ++ Generates fundamental group from this cubical complex.
      ++ This works by converting to a DeltaComplex and then generating the
      ++ fundamentalGroup from that. This is not the most efficient method
      ++ because the conversion generates lots of higher order faceMaps
      ++ which are not used.
      ++ TODO write a faster implementation which only generates edge and
      ++ square faceMaps.
      ++ This version has options to:
      ++ simplify - if true attempts to simplify the group presentation
      ++ trace - if true outputs trace of simplification rules used.
    homology : (a : %) -> List(Homology)
      ++ calculate homology using SmithNormalForm
    boundary : (s : %) -> %
      ++ Boundary operator. Step down to the next lower dimension, this is
      ++ like hollowing out the complex.
      ++ It takes the highest dimension entries and replaces them with
      ++ their boundaries, cancelling out where it can.
    chain : (s : %) -> ChainComplex
      ++ returns a matrix sequence representing the face maps
      ++ in linear algebra form
    coerce : (s : %) -> DeltaComplex(VS)
      ++ coerce CubicalComplex to DeltaComplex

  Impl ==> add

   -- Representation holds whole Cubical Complex. This consists of
   -- a vertex set, here represented as a vertex list so that we can
   -- index it.
   -- Also a list of hypercubes.
   Rep := Record(VERTSET : List(VS), CUBE : List(CubicalFacet))

   -- constructor where the vertex set and cubes are supplied
   cubicalComplex(v : List(VS), fs : List(CubicalFacet)) : % ==
       [v, fs]

   -- constructor where the vertex set and cubes are supplied
   cubicalComplex(v : List(VS), fs : List(List(Segment(Integer)))) : % ==
       res := []$List(CubicalFacet)
       for f in fs repeat
           res := concat(res, cubicalFacet(1, f))
       --print("cubicalComplex(" << fs << ") res=" << res)
       cubicalComplex(v, res)

   -- constructor where the vertex set and cubes are supplied
   cubicalComplex(v : List(VS), fs : List(List(List(Integer)))) : % ==
       res := []$List(CubicalFacet)
       for f in fs repeat
           res := concat(res, cubicalFacet(1, f))
       --print("cubicalComplex(" << fs << ") res=" << res)
       cubicalComplex(v, res)

   -- construct empty cubical complex
   cubicalComplex(v : List(VS)) : % ==
       res := []$List(CubicalFacet)
       [v, res]

   -- find the maximum index value.
   maxIndex(s : %) : NNI ==
       res : NNI := 0
       facs : List(CubicalFacet) := s.CUBE
       for fac in facs repeat
           i:List(Segment(Integer)) := getIntervals(fac)
           m := #i
           if m>res then res := m
       res

   -- Add a facet to this cubical complex
   -- If the facet is also in the list then add the multipliers.
   addCube(a : %, f : CubicalFacet) : % ==
       res : List(CubicalFacet) := a.CUBE
       res2 := []$List(CubicalFacet)
       -- check for duplicates
       found : Boolean := false
       for orf in res repeat
           if getIntervals(orf)=getIntervals(f)
               then
                   m : Integer := getMult(orf) + getMult(f)
                   if m~=0 then
                       res2 := concat(res2, setMult(orf, m))
                   found := true
               else
                   res2 := concat(res2, orf)
       if not found then
           res2 := concat(res2, f)
       --print("addCube(" << a << ", " << f << ") res=" << res2)
       cubicalComplex(a.VERTSET, res2)

   -- Boundary operator. Step down to the next lower dimension, this is
   -- like hollowing out the complex.
   -- It takes the highest dimension entries and replaces them with
   -- their boundaries, cancelling out where it can.
   boundary(s : %) : % ==
       res : % := cubicalComplex(s.VERTSET)
       --facets := []$List(CubicalFacet)
       for facet in s.CUBE repeat
           x : List(CubicalFacet) := boundary(facet)
           --print("boundary(" << s.CUBE << ") x=" << x)
           for y in x repeat
               --facets := concat(facets, y)$List(CubicalFacet)
               res := addCube(res, y)
       res

   -- A function to grade faces according to the number of
   -- non-degenerate intervals. This nests the lists according to
   -- their order. Vertices, edges, squares and so on.
   grade(s : %) : List(List(CubicalFacet)) ==
       simp : List(CubicalFacet) := s.CUBE
       -- set maxGrade to be the maximum number of non-degenerate
       -- intervals in any face
       maxGrade : NNI := 0
       for face in simp repeat
           if order(face) > maxGrade then maxGrade := order(face)
       --print("grade(" << s << ") maxGrade=" << maxGrade << " simp=" << simp)
       res := []$List(List(CubicalFacet))
       if maxGrade < 0 then return res
       -- create empty list for each grade
       for a in 0..maxGrade repeat
           res := concat(res, []$List(CubicalFacet))
       for face in simp repeat
           -- order is the number of non-degenerate intervals
           gr : Integer := order(face) + 1
           --print("grade(" << s << ") face=" << face << " gr=" << gr)
           if gr > 0
               then
                   res.gr := concat(res.gr, face)
               -- else
               --    print("grade face:" << face << " gr=" << res << "<1")
       --print("grade(" << s << ") res=" << res)
       res

   -- local function used in listUnion in addImpliedFaces
   isNewFace?(lst : List(CubicalFacet), b : CubicalFacet) : Boolean ==
       --print("isNewFace?(" << a << ", " << b << ")")
       for a in lst repeat
           if sameFace?(a, b) then return false
           --if member?(b, a)$List(CubicalFacet) then return false
       true

   -- local function used in addImpliedFaces
   listUnion(a : List(CubicalFacet), b : List(CubicalFacet)) : _
             List(CubicalFacet) ==
       res := []$List(CubicalFacet)
       for a1 in a repeat
           res := concat(res, a1)
       for b1 in b repeat
           if isNewFace?(a, b1) then
               res := concat(res, b1)
       --print("listUnion(" << a << ", " << b << ") = " << res)
       res

   -- A function to take a set of faces and add those that are implied
   -- by the cubical complex conventions (if they don't already exist).
   -- For example, if the input is a square [1..2, 1..2] then we would add
   -- its edges [1..1, 1..2], [2..2, 1..2], [1..2, 1..1], and [1..2, 2..2].
   addImpliedFaces(s : %) : List(List(CubicalFacet)) ==
       --print("addImpliedFaces s=" << s)
       -- start at highest grade and work down
       inputList : List(List(CubicalFacet)) := reverse(grade(s))
       res := []$List(List(CubicalFacet))
       maxGrade : NNI := #inputList
       --print("addImpliedFaces inputList=" << inputList <<
       --      " maxGrade=" << maxGrade)
       if (maxGrade) < 1 then
           --print("addImpliedFaces return because maxGrade=" << maxGrade)
           return res
       -- create an empty list for each grade
       for a in 1..(maxGrade) repeat
           res := concat(res, []$List(CubicalFacet))
       newFaces := []$List(CubicalFacet)
       for gr in inputList for grn in 1..(maxGrade) repeat
           --print("addImpliedFaces grade number grn=" << grn << " gr=" << gr)
           -- add old faces
           for face in gr repeat
               res.grn := concat(res.grn, face)
           -- add new faces
           for face in newFaces repeat
               res.grn := concat(res.grn, face)
           -- print("addImpliedFaces empty newFaces. res.(" << grn
           --       <<")=" << res.grn)
           newFaces := []$List(CubicalFacet)
           --print("addImpliedFaces res.(" << grn <<")=" << res.grn)
           for face in res.grn repeat
               len := qcoerce(order(face) -1)@NNI
               if len > -1 then
                   newFaces := listUnion(newFaces, allSubsets(face, len, len))
           -- print("addImpliedFaces get next grade res=" << res <<
           --       " newFaces=" << newFaces)
       --print("addImpliedFaces result=" << reverse(res))
       reverse(res)

   -- Forms product of two cubical complexes.
   -- For more detailed explanation of cube product code see:
   -- \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/cubical/}
   product(a : %, b : %) : % ==
       cubesa : List(CubicalFacet) := a.CUBE
       cubesb : List(CubicalFacet) := b.CUBE
       -- How do we represent the points? This should be the Cartesian
       -- product of the points in 'a' and the points in 'b', so this
       -- would increase the dimension of the points. But here
       -- we do not know how these points are represented, we can only
       -- do Abelian group operations (+, - or set to 0).
       -- The following is not correct but it is the best I can do for now.
       vs : List(VS) := []
       for pa in a.VERTSET repeat
           for pb in b.VERTSET repeat
               pp:VS := pa+pb
               vs := concat(vs,pp)
       res := []$List(CubicalFacet)
       for cubea in cubesa repeat
           for cubeb in cubesb repeat
               p : CubicalFacet := product(cubea, cubeb)
               res := concat(res, p)
       cubicalComplex(vs, res)

   -- Generates fundamental group from this cubical complex.
   -- This works by converting to a DeltaComplex and then generating the
   -- fundamentalGroup from that. This is not the most efficient method
   -- because the conversion generates lots of higher order faceMaps
   -- which are not used.
   -- This will attempt to simplify the group presentation, if you
   -- don't want this use alternative function of same name.
   -- TODO write a faster implementation which only generates edge and
   -- square faceMaps.
   fundamentalGroup(s : %) : GroupPresentation ==
       fundamentalGroup(s, true, false)

   -- Generates fundamental group from this cubical complex.
   -- This works by converting to a DeltaComplex and then generating the
   -- fundamentalGroup from that. This is not the most efficient method
   -- because the conversion generates lots of higher order faceMaps
   -- which are not used.
   -- TODO write a faster implementation which only generates edge and
   -- square faceMaps.
   -- This version has options to:
   -- simplify - if true attempts to simplify the group presentation
   -- trace - if true outputs trace of simplification rules used.
   fundamentalGroup(s:%, simplify : Boolean, trace : Boolean) : _
                  GroupPresentation ==
       dc : DeltaComplex(VS) := deltaComplex(s pretend FiniteCubicalComplex(VS))
       fundamentalGroup(dc, simplify, trace)

   -- calculate homology using SmithNormalForm
   homology(s : %) : List(Homology) ==
       dc : DeltaComplex(VS) := deltaComplex(s pretend FiniteCubicalComplex(VS))
       homology(dc)$DeltaComplex(VS)

   -- returns a matrix sequence representing the face maps
   -- in linear algebra form
   chain(s : %) : ChainComplex ==
       dc : DeltaComplex(VS) := deltaComplex(s pretend FiniteCubicalComplex(VS))
       cc : ChainComplex := chain(dc)$DeltaComplex(VS)
       cc

   -- Returns true if x equals y.
   -- That is, they contain the same simplexes, although not
   -- necessarily in the same order.
   -- TODO - Assumes point set is exactly the same, for more generality
   -- we should allow the points to be defined in a different order.
   _=(a : %, b : %) : Boolean ==
       as : List(CubicalFacet) := a.CUBE
       bs : List(CubicalFacet) := b.CUBE
       --print("equal:" << as << ", " << bs)
       len : NNI := #as
       if len ~= #bs then return false
       flags : List(Boolean) := [false for x in as] -- flag for every entry in b
       --print("empty flags " << flags)
       for a1 in as repeat
           x : NNI := 1
           while (a1 ~= bs.x) or (flags.x) repeat
               --print("loop:" << x << " flags" << flags)
               x := x + 1
               if x > #as then return false
           flags.x := true
           --print("flags." << x << "=true " << flags)
       --print("final flags " << flags)
       for f in flags repeat
           if f=false then return false
       true

   -- output
   coerce(s : %) : OutputForm ==
       res := empty()$OutputForm
       if empty?(s.CUBE) then return bracket(message("empty"))
       for a in s.CUBE repeat
           -- a is OrientedFacet
           res := vconcat(res, a::OutputForm)
       --print("FiniteCubicialComplex coerce num=" << #(s.CUBE))
       res

   -- coerce CubicalComplex to DeltaComplex
   coerce(s : %)  :  DeltaComplex(VS) ==
       deltaComplex(s pretend FiniteCubicalComplex(VS))

)if false
\section{Delta Complex}

The FiniteSimplicialComplex representation defines faces, of any
dimension, by their vertices. This is an efficient way to define
them, a disadvantage is that intermediate parts, such as edges, are
not indexed. It is sometimes useful to be able to do this, for
example, when generating homotopy groups such as the fundamental group.

Advantages of Simpectial Complexes:
\begin{itemize}
\item Easier to construct and to understand
\item Some operations are easier on simplectial complexes.
\end{itemize}
Advantages of Delta Complexes
\begin{itemize}
\item Can sometimes be more compact, for example, use less points.
\item Easier to construct chains, homology and homotopy from delta
complexes so they are a good intermediate step.
\end{itemize}

So it is worthwhile implementing both types in code with conversions
between them.

It is possible to convert from and to simplectial complexes like this:
\begin{verbatim}
simplectial complexes ->  delta complexes -> simplectial complexes
\end{verbatim}
and get back to where we started. However it is not always possible
to start with delta complexes and get back to where we started.
\begin{verbatim}
delta complexes -> simplectial complexes ->  delta complexes
\end{verbatim}
This is because the more compact coding of delta complexes is hard to
translate because it can only be done by adding extra points. Also the
two types are not exactly isomorphic as there are special cases which
can only be coded in one type.

There are also other codings of cell complexes such as cubical
complexes, implemented below.

\subsection{Creating Delta Complexes}
Here are 3 ways to construct a delta complex:
\begin{itemize}
\item From DeltaComplexFactory - Some simple complexes are provided
by factory functions.
\item From FiniteSimplicialComplex - Sometimes it is easier to
construct a simplicial complex first and then convert.
\item Directly from index lists.
\end{itemize}
We cab easily construct from factory functions like this:
\begin{verbatim}
(5) -> cD := circle()$DeltaComplexFactory(Integer)

   (5)
         1D:[[1,- 1]]
           0D:[[0]]
                               Type: DeltaComplex(Integer)
\end{verbatim}
Constructing from  a simplicial complex first and then converting:
\begin{verbatim}
(6) -> cS := sphereSurface(2)$SCF

   (6)  points 1..3
           (1,2)
          -(1,3)
           (2,3)
                   Type: FiniteSimplicialComplex(Integer)
(7) -> deltaComplex(cS)

   (7)
         1D:[[1,- 2],[- 1,3],[2,- 3]]
               0D:[[0],[0],[0]]
                               Type: DeltaComplex(Integer)
\end{verbatim}
Here we construct directly from index lists:
\begin{verbatim}
(8) -> deltaComplex([],1, [[[1, -1]]])$DeltaComplex(Integer)

   (8)
          1D:[[1,- 1]]
            0D:[[0]]
                               Type: DeltaComplex(Integer)
\end{verbatim}

\subsection{Representation of Delta Complexes}
The representation consists of:
\begin{itemize}
\item vertexset - As for simpectial complex.
\item maps - Highest dimension first down to dimension zero (points)
\end{itemize}
The array for each point is either [0] for a used point or [] for an
unused point. By 'unused point' I mean a point that is not part of
this delta complex but its position is held for future use.
)endif

)abbrev domain DELTAC DeltaComplex
++ Author: Martin Baker
++ Description:
++   Similar to Simplicial Complex but faces (edges, triangles, etc.)
++   are indexed by 'face maps' into the next lower face map until
++   we get down to the vertices.
++   for more documentation see:
++   http://www.euclideanspace.com/prog/scratchpad/mycode/topology/delta/
++ Date Created: Feb 2016
++ Basic Operations:
++ Related packages:
++ Related categories:
++ Related Domains: FiniteSimplicialComplex is a simpler and more
++    compact representation which can be used if edges, triangles,
++    etc. don't need to be indexed.
++ Also See:
++ AMS Classifications:
++ Keywords: topology, finite, simplicial, complex, edge, triangle
++ Examples:
++ References:

DeltaComplex(VS : AbelianGroup) : Exports == Impl where
  NNI==> NonNegativeInteger
  x<<y ==> hconcat(x::OutputForm, y::OutputForm)

  Exports ==> SetCategory() with
    deltaComplex : (v : List(VS), numPoints:NNI,_
                    f : List(List(List(Integer)))) -> %
      ++ constructor where the simplices are supplied
    deltaComplex : (fsc : FiniteSimplicialComplex(VS)) -> %
      ++ construct from FiniteSimplicialComplex. This builds indexes
      ++ of edges, triangles and so on.
    deltaComplex : (fsc : FiniteSimplicialComplex(VS),_
                trace : Boolean) -> %
    deltaComplex : (fsc : FiniteCubicalComplex(VS)) -> %
      ++ construct from FiniteCubicalComplex. This builds indexes of
      ++ edges, squares and so on.
    link : (a : NNI, b : NNI) -> %
      ++ a simplicial complex with one link
    triangle : (a : NNI, b : NNI, c : NNI) -> %
      ++ a simplicial complex with one triangle
    faceMap : (s : %, n : NNI) -> List(List(Integer))
      ++ returns an individual face map specified by n.
      ++ Where 'n' is the dimension required, so n=1 returns
      ++ one dimensional faces (edges), n=2 returns
      ++ two dimensional faces (triamgles), and so on.
      ++ used by fundamentalGroup.
    chain : (s : %) -> ChainComplex
      ++ returns a matrix sequence representing the face maps
      ++ in linear algebra form
    coChain : (s : %) -> CoChainComplex(VS)
      ++ returns a matrix sequence representing the face maps
      ++ in linear algebra form
    oneSkeleton : (s : %) -> UndirectedGraph(NNI)
      ++ generates graph AKA 1-skeleton
    fundamentalGroup : (s : %) -> GroupPresentation
      ++ Generates fundamental group from this simplicial complex.
    fundamentalGroup : (s : %, simplify : Boolean, trace : Boolean
                       ) -> GroupPresentation
      ++ Generates fundamental group from this simplicial complex.
    homology : (a : %) -> List(Homology)
      ++ calculate homology using SmithNormalForm
    coHomology : (a : %) -> List(Homology)
      ++ calculate cohomology using SmithNormalForm
    coerce : (s : %) -> FiniteSimplicialComplex(VS)
      ++ coerce DeltaComplex to FiniteSimplicialComplex
    -- coerce : (s : %) -> FiniteCubicalComplex(VS)
    -- ++ coerce CubicalDeltaComplex to FiniteCubicalComplex

  Impl ==> add

   -- Representation holds whole Complex. This consists of
   -- a vertex set, here represented as a vertex list so that we can
   -- index it.
   -- Also a sequence of tables (known as 'face maps') representing
   -- edges, triangles, tetrahedrons, etc. containing indexes into
   -- the next lower map.
   -- Since edges, triangles, tetrahedrons, etc. are oriented we use
   -- a positive or negative index to indicate winding direction.
   Rep := Record(VERTSET : List(VS),_
                 MAPS : List(List(List(Integer))))

   -- local function to calculate 0 dimensional simplexes (points)
   -- from the indexes of 1 dimensional simplexes (lines)
   -- where:
   -- dim1 is one dimensional entries (lines).
   -- n is minimum number of points.
   dim1todim0(dim1 : List(List(Integer)),_
              n : NNI) : List(List(Integer)) ==
       -- first determine number of points
       m:NNI := n
       for smp in dim1 repeat
           for m2 in smp repeat
               m3 : NNI := abs(m2) :: NNI
               if m3>m then m := m3
       -- now set 'pointsUsed' which holds list with
       -- true for points which are used.
       pointsUsed : List(Boolean) := new(m,false)
       for smp in dim1 repeat
           for m2 in smp repeat
               m3 : NNI := abs(m2) :: NNI
               pointsUsed.m3 := true
       -- now generate array with entry for each point
       res : List(List(Integer)) := _
           [if x then [0] else [] for x in pointsUsed]
       res

   -- constructor where the vertex set and simplices are supplied
   deltaComplex(v : List(VS),numPoints:NNI,_
             f : List(List(List(Integer)))) : % ==
       f0 := dim1todim0(f.last,numPoints)
       fp := concat(f,f0)
       [v, fp]

   -- construct from FiniteSimplicialComplex. This builds indexes of
   -- edges, triangles and so on.
   deltaComplex(fsc : FiniteSimplicialComplex(VS)) : % ==
       deltaComplex(fsc,false)

   -- construct from FiniteSimplicialComplex with trace. This builds
   -- indexes of edges, triangles and so on.
   deltaComplex(fsc : FiniteSimplicialComplex(VS),_
                trace : Boolean) : % ==
       fscRep := fsc pretend  Record(VERTSET : List(VS),
                         NUMPOINTS : NNI,
                         SIMP : List(OrientedFacet))
       -- first grade and add implied faces
       allFacets : List(List(OrientedFacet)) := addImpliedFaces(fsc)
       if trace then
           print(message("deltaComplex(") << fsc <<
                 message(") allFacets=") << allFacets)
       -- sort each grade
       allFacetsGraded := [sort(l) for l in allFacets
                          ]$List(List(OrientedFacet))
       if trace then
           print(message("deltaComplex allFacetsGraded=") << allFacetsGraded )
       -- now change base indexed to hierarchical indexed
       faceMaps := []$List(List(List(Integer)))
       previousFaceList := []$List(OrientedFacet)
       for gradeList in allFacetsGraded for grn in 1..(#allFacetsGraded) repeat
           if trace then
               print(message("deltaComplex grad num=") << grn <<
                     message("=") << gradeList)
           -- start with a list of empty lists
           map1 := []$List(List(Integer))
           if grn = 1 then
               -- points
               -- Special handling for points because they do not
               -- index into lower dimension.
               map2 := []$List(List(Integer))
               for x in gradeList repeat
                   -- x is an OrientedFacet
                   fm : List(Integer) := getIndexesSigned(x)
                   -- fm is signed list of vertices
                   map2 := cons(fm, map2)
                   if trace then
                       print(message("  deltaComplex getIndexesSigned(") <<
                             x << message(") fm=") << fm <<
                             message(" map2=") << map2)
               -- first determine number of points
               m:NNI := fscRep.NUMPOINTS
               for smp in map2 repeat
                   for m2 in smp repeat
                       m3 : NNI := abs(m2) :: NNI
                       if m3>m then m := m3
               if trace then
                   print(message("  deltaComplex m=") << m)
               -- now set 'pointsUsed' which holds list with
               -- true for points which are used.
               pointsUsed : List(Boolean) := new(m,false)
               for smp in map2 repeat
                   for m2 in smp repeat
                       m3 : NNI := abs(m2) :: NNI
                       pointsUsed.m3 := true
               -- now generate array with entry for each point
               map1 := [if x then [0] else [] for x in pointsUsed]
               if trace then
                   print(message("  deltaComplex m=") << m <<
                         message(" map2=") << map2 <<
                         message(" map1=") << map1)
               map1 := reverse(map1)
           else
               -- edges and above
               -- we need to build up a map from oldFaceHigh to oldFacesLow
               for oldFaceHigh in gradeList repeat
                   --print("  deltaComplex oldFaceHigh=" << oldFaceHigh )
                   oldFacesLow : List(OrientedFacet) := boundary(oldFaceHigh)
                   --print("  deltaComplex oldFacesLow=" << oldFacesLow )
                   newIndexes := []$List(Integer)
                   for oldFaceLow in oldFacesLow repeat
                       if trace then
                           print(message("    deltaComplex oldFaceLow=") <<
                                 oldFaceLow << message(" previousFaceList=")
                                 << previousFaceList << _
                                 message(" first(allFacetsGraded) =") <<
                                 first(allFacetsGraded))
                       i := position(oldFaceLow, previousFaceList
                                    )$OrientedFacet
                       if i = 0 then
                           print(message("  deltaComplex oldFaceLow=")
                                 << oldFaceLow <<
                                 message(" not found in previousFaceList=")
                                 << previousFaceList )
                           error concat("can not find index:", string(i))
                       newIndexes := cons(i, newIndexes)
                   map1 := cons(reverse!(newIndexes), map1)
           faceMaps := cons(reverse!(map1), faceMaps)
           previousFaceList := gradeList
       vs : List(VS) := fscRep.VERTSET
       if trace then
           print(message("deltaComplex faceMaps=") << faceMaps )
       [vs, faceMaps]

   -- construct from FiniteCubicalComplex. This builds indexes of
   -- edges, squares and so on.
   deltaComplex(fsc : FiniteCubicalComplex(VS)) : % ==
       -- first grade and add implied faces
       allFacets : List(List(CubicalFacet)) := addImpliedFaces(fsc)
       --print("deltaComplex(" << fsc << ") allFacets=" << allFacets)
       -- sort each grade
       allFacetsGraded := []$List(List(CubicalFacet))
       for gradedList in allFacets repeat
           --print("deltaComplex gradedList=" << gradedList )
           allFacetsGraded := concat(allFacetsGraded, sort(gradedList))
       --print("deltaComplex allFacetsGraded=" << allFacetsGraded )
       -- now build face maps
       faceMaps := []$List(List(List(Integer)))
       previousFaceList := first(allFacetsGraded)
       for gradeList in allFacetsGraded for grn in 1..(#allFacetsGraded) repeat
           -- start with a list of empty lists
           grn = 1 => iterate
           -- edges and above
           -- we need to build up a map from oldFaceHigh to oldFacesLow
           map1 := []$List(List(Integer))
           for oldFaceHigh in gradeList repeat
               oldFacesLow : List(CubicalFacet) := boundary(oldFaceHigh)
               --print("  deltaComplex oldFaceHigh=" << oldFaceHigh << _
               --      " oldFacesLow=" << oldFacesLow )
               newIndexes := []$List(Integer)
               for oldFaceLow in oldFacesLow repeat
                   i := position(oldFaceLow, previousFaceList)$CubicalFacet
                   if i = 0 then
                       print(message("  deltaComplex oldFaceLow=")
                             << oldFaceLow <<
                             message(" not found in previousFaceList=")
                             << previousFaceList )
                       error concat("deltaComplex: can not find index:",
                                    string(i))
                   newIndexes := cons(i, newIndexes)
               map1 := cons(reverse!(newIndexes), map1)
           faceMaps := cons(reverse!(map1), faceMaps)
           previousFaceList := gradeList
       --print("deltaComplex faceMaps=" << faceMaps )
       vs : List(VS) := (fsc pretend Record(VERTSET : List(VS),
                                      SIMP : List(List(NNI)))).VERTSET
       m := maxIndex(fsc)
       f0 := dim1todim0(faceMaps.last,m)
       fp : List(List(List(Integer))) := concat(faceMaps,f0)
       [vs, fp]

   -- a simplicial complex with one link
   link(a : NNI, b : NNI) : % ==
       vs : List(VS) := []
       simp : List(List(List(Integer))) := [[[a, b]]]
       points : List(List(Integer)) := dim1todim0(first(simp),2)
       simp := concat (simp,points)
       [vs, simp]

   -- a simplicial complex with one triangle
   triangle(a : NNI, b : NNI, c : NNI):% ==
       vs : List(VS) := []
       edges : List(List(Integer)) := [[a, b], [a, c], [b, c]]
       triangles : List(List(Integer)) := [[1, -2, 3]]
       points : List(List(Integer)) := dim1todim0(edges,3)
       simp : List(List(List(Integer))) := [triangles, edges, points]
       [vs, simp]

   -- returns an individual face map specified by n.
   -- Where 'n' is the dimension required + 1, so
   -- n=1 returns zero dimensional faces (points),
   -- n=2 returns one dimensional faces (edges),
   -- n=3 returns two dimensional faces (triangles),
   -- and so on. Used by fundamentalGroup.
   faceMap(s : %, n : NNI) : List(List(Integer)) ==
       maps1 : List(List(List(Integer))) := reverse(s.MAPS)
       --print(message "faceMap maps1=" << maps1 << " n=" << n)
       if n > #maps1 then return []$List(List(Integer))
       maps1.n

   -- returns a matrix sequence representing the face maps
   -- in linear algebra form
   chain(s : %) : ChainComplex ==
       res := []$List(Matrix(Integer))
       maps1 : List(List(List(Integer))) := reverse(s.MAPS)
       n_rows : NNI := 1
       --print(message "chain maps1=" << maps1)
       for faces in maps1 repeat
           n_cols := #(faces)
           --print(message "chain faces=" << faces << _
           --      message " n_rows=" << n_rows << _
           --      message " n_cols=" << n_cols )
           -- zero constructs a Matrix containing '0's.
           m : Matrix(Integer) := zero(n_rows, n_cols)
           --print(message "chain zero matrix=" << m << _
           --      message " nrows=" << n_rows << _
           --      message " ncols=" << n_cols)
           for u in faces for nu in 1..(#faces) repeat
               for v in u repeat
                   v2 : Integer := abs(v)
                   if v2 > 0 then
                       ele : Integer := (v > 0 => 1; -1)
                       --print(message " col=" << v2 << _
                       --  message " row=" << nu << _
                       --  message " n_cols=" << n_cols << " ele=" << ele)
                       setelt!(m, v2, nu, qelt(m, v2, nu) + ele)
           res := cons(m, res)
           -- adjacent matrices can be multiplied together so the
           -- number of rows in the next matrix is equal to the
           -- number of columns in this matrix.
           --print(message "chain res=" << res)
           n_rows := n_cols
       -- add matrix with zero cols
       --print(message "chain n_rows=" << n_rows)
       m : Matrix(Integer) := new(n_rows, 0, 0)
       res := cons(m, res)
       chainComplex(reverse!(res))

   -- returns a matrix sequence representing the face maps
   -- in linear algebra form
   -- For more information:
   -- \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/chain/cochain/index.htm}
   coChain(s : %) : CoChainComplex(VS) == coChainComplex(chain(s))

   -- calculate homology using SmithNormalForm
   homology(a : %) : List(Homology) ==
       --print("DeltaComplex homology("  << a << ")")
       cc : ChainComplex := chain(a)
       validate(cc)
       homology(cc)

   -- calculate cohomology using SmithNormalForm
   -- For more information:
   -- \url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/homology/cohomol/index.htm}
   coHomology(a : %) : List(Homology) ==
       --print("DeltaComplex cohomology("  << a << ")")
       cc : CoChainComplex(VS) := coChain(a)
       validate(cc)
       coHomology(cc)

   -- Generates graph AKA 1-skeleton.
   -- Used to generate fundamental group
   oneSkeleton(s : %) : UndirectedGraph(NNI) ==
       ARROW ==> Record(name : String, arrType : NNI, fromOb : NNI, toOb : NNI,
                        xOffset : Integer, yOffset : Integer, map : List NNI)
       OBJT ==> Record(value : NNI, posX : NNI, posY : NNI)
       GS := UndirectedGraph(NNI)
       nodes := []$List(OBJT)
       edges := []$List(ARROW)
       if #(s.MAPS) < 2 then
           return initial()$GS
       m : NNI := #second(reverse(s.MAPS))
       edgeIndexes : List(List(Integer)) := second(reverse(s.MAPS))
       --print("oneSkeleton edgeIndexes=" << edgeIndexes << " m=" << m)
       for x in edgeIndexes repeat
           --print("oneSkeleton x=" << x)
           from1 := qcoerce(abs(first(x)))@NNI
           if from1 > m then m := from1
           to1 := qcoerce(abs(second(x)))@NNI
           if to1 > m then m := to1
           y : ARROW := ["x", 0::NNI, from1, to1, 0::Integer, 0::Integer, []]
           edges := concat(edges, y)$List(ARROW)
       --print("oneSkeleton nodes=" << nodes << " edges=" << edges  << _
       --     " maxVerts=" << m)
       for m1 in 1..m repeat
           nodes := concat(nodes, [m1, 0::NNI, 0::NNI])$List(OBJT)
       undirectedGraph(nodes, edges)$GS

   -- local function to list all the links in a given tree
   linksInTree(res : List(List(Integer)), a : Tree(Integer)
              ) : List(List(Integer)) ==
       --print("linksInTree value=" << value(a) << " children=" << children(a))
       fromNode : Integer := value(a)
       for x in children(a) repeat
           toNode : Integer := value(x)
           newLink : List(Integer) := [fromNode, toNode]
           res := concat(res, newLink)
       for x in children(a) repeat
           res := linksInTree(res, x)
       res

   -- local function used by fundamentalGroup
   -- returns true if edge is contained in edges where:
   -- edge is specified by the indices of its start and end values
   -- edges is a list of edges
   -- match is regardless of direction of link
   containsEdge?(edge : List(Integer), edges : List(List(Integer))
                ) :  Boolean ==
       if #edge < 2 then return false
       s : Integer := first(edge)
       t : Integer := second(edge)
       for e in edges repeat
           if #e > 1 then
               s2 : Integer := first(e)
               t2 : Integer := second(e)
               if (abs(s) = abs(s2)) and (abs(t) = abs(t2)) then return true
               if (abs(s) = abs(t2)) and (abs(t) = abs(s2)) then return true
       false

   -- Generates fundamental group from this delta complex.
   fundamentalGroup(s : %) : GroupPresentation ==
       fundamentalGroup(s, true, false)

   fundamentalGroup(s : %, simplify : Boolean, trace : Boolean
                   ) : GroupPresentation ==
       -- Generates fundamental group from this delta complex.
       if trace then print(message("fundamentalGroup(") << s << message(")"))
       -- fundamental group is about triangles (or squares for cubical
       -- complex) so get face map for triangles (2 dimensional)
       -- compute edge-path group
       graph : UndirectedGraph(NNI) := oneSkeleton(s)
       if trace then print("fundamentalGroup graph=" << graph )
       -- choose a spanning tree for the 1-skeleton
       span : Tree(Integer) := spanningTreeNode(graph, 1::NNI
                                               )$UndirectedGraph(NNI)
       if trace then print(message("fundamentalGroup span=") << span)
       edges : List(List(Integer)) := faceMap(s, 2::NNI)
       --res := []$List(List(Integer))
       res : List(List(Integer)) := linksInTree(res, span)
       if trace then
           print(message "fundamentalGroup edges=" << edges << _
                 message " res=" << res)
       edgesInTree : List(Boolean) := [false for y in edges]
       for edge in edges for edgen in 1..(#edges) repeat
           --if member?(unsignedEdge(edge), res) then edgesInTree.edgen := true
           if containsEdge?(edge, res) then edgesInTree.edgen := true
       if trace then
           print(message("fundamentalGroup linksInTree=") << res <<
                 message(" edgesInTree=") << edgesInTree)
       polygons : List(List(Integer)) := faceMap(s, 3::NNI)
       -- generate rules, these come from 2 dimensional triangles in the
       -- case of simplicial or squares in the case of cubical
       rules := []$List(List(Integer))
       if trace then print(message("fundamentalGroup polygons=") << polygons )
       for t in polygons repeat
           rule := []$List(Integer)
           for vertn in 1..(#t) repeat
               if not edgesInTree.(abs(t.vertn)) then
                   rule := concat(rule, t.vertn)
           if not empty?(rule) then rules := concat(rules, rule)
       if trace then print(message("fundamentalGroup rules=") << rules)
       -- groups generators: edges in 1-skeleton
       -- groups relations: for every 2-simplex
       --                   e0 e1^-1 e2
       --                   (e0, e1, e2 are edges)
       -- set m to maximum index in the face map
       m : NNI := #edges
       generators := []$List(NNI)
       for x in 1..m repeat
           if not edgesInTree.x then
               generators := concat(generators, x::NNI)
       --print("fundamentalGroup generators=" << generators )
       gp : GroupPresentation := groupPresentation(generators, rules
                                                  )$GroupPresentation
       --print("fundamentalGroup GroupPresentation unsimplified=" << gp )
       if not simplify then return gp
       simplify(gp, trace)$GroupPresentation

   -- Local function used by '=' to check if two simplexes are equal.
   -- To be equal they must contain the same indexes, wound in the same
   -- direction but not necessarily starting at the same value.
   equalSimplex(a : List(Integer), b : List(Integer)) : Boolean ==
       --print("equalSimplex:" << a << ", " << b)
       len : NNI := #a
       if len ~= #b then return false
       if len = 0 then return true
       fst : Integer := first a
       offset : Integer := position(fst, b)
       if offset < 1 then return false
       for n in 1..#a repeat
           if a.n ~= b.offset then return false
           offset := offset + 1
           if offset > #b then offset := 1
       true

   -- Returns true if x equals y.
   -- That is, they contain the same simplexes, although not
   -- necessarily in the same order.
   -- TODO - Assumes point set is exactly the same, for more
   -- generality we should allow the points to be defined in a
   -- different order.
   _=(a : %, b : %) : Boolean ==
       as := a.MAPS
       bs := b.MAPS
       --print("equal:" << as << ", " << bs)
       len : NNI := #as
       if len ~= #bs then return false
       true

   -- Output. Highest dimension first.
   coerce(s : %) : OutputForm ==
       res := empty()$OutputForm -- holds result
       if empty?(s.MAPS) then
           return hconcat([message "1D: points 1.."])
       tabNum : Integer := #(s.MAPS) - 1
       for a in s.MAPS repeat
           row := hconcat([message(" "), tabNum::OutputForm,
                           message("D:"), a::OutputForm])
           res := vconcat(res, row)
           tabNum := tabNum - 1
       res

   -- coerce DeltaComplex to FiniteSimplicialComplex
   coerce(s : %) : FiniteSimplicialComplex(VS) ==
       simplicialComplexIfCan(s pretend DeltaComplex(VS)) _
       :: FiniteSimplicialComplex(VS)

   -- coerce CubicalDeltaComplex to FiniteCubicalComplex
--   coerce(s : %) : FiniteCubicalComplex(VS) ==
--     cubicalComplex(s pretend CubicalDeltaComplex(VS))

)if false
\section{SimplicialComplex Factory}

SimplicialComplexFactory constructs example simplicial complexes for us.

There are certain elementary complexes that we frequently need to use,
it is therefore very useful to be able to generate them quickly and
easily.
)endif

)abbrev package SIMPCF SimplicialComplexFactory
++ Author: Martin Baker
++ Date Created: Jan 2016
++ Description:
++  Provides functions to construct various simplicial complexes.
PI ==> PositiveInteger
NNI==> NonNegativeInteger
x<<y ==> hconcat(x::OutputForm, y::OutputForm)

SimplicialComplexFactory(VS : AbelianGroup) : with
  sphereSolid : (dim : NNI) -> FiniteSimplicialComplex(VS)
    ++ Generate a simplicial complex equivalent to a solid hyper-sphere.
    ++ That is, a solid volume with no holes in it, so we use a single
    ++ n-dimensional simplex.
  sphereSurface : (dim : NNI) -> FiniteSimplicialComplex(VS)
    ++ Generate a simplicial complex equivalent to the surface of a
    ++ hyper-sphere. That is, the surface of a volume with no holes
    ++ in it, so we use a minimal triangulation of the faces of a
    ++ single simplex.
  line : () -> FiniteSimplicialComplex(VS)
    ++ create a simplicial complex containing a line.
  torusSurface : () -> FiniteSimplicialComplex(VS)
    ++ generate a minimal triangulation of the surface of a
    ++ 3-dimensional torus.
  band : () -> FiniteSimplicialComplex(VS)
    ++ Generate a minimal triangulation of the surface of a cylinder
    ++ without ends
  moebiusBand : () -> FiniteSimplicialComplex(VS)
    ++ Generate a minimal triangulation of the surface of a Moebius band.
    ++ A one sided surface with a single external boundary line.
  projectiveSpace : (n : Integer) -> FiniteSimplicialComplex(VS)
    ++ Generate a minimal triangulation of the surface of a projective plane.
    ++ A one sided surface which cannot be embedded in 3D space without
    ++ intersecting itself. There are no external boundaries, every edge
    ++ connects two faces.
    ++ (A punctured projective plane is equivalent to a Moebius band)
  kleinBottle : () -> FiniteSimplicialComplex(VS)
    ++ generate a minimal triangulation of the surface of a Klein bottle.
  dunceHat : () -> FiniteSimplicialComplex(VS)
    ++ Generate a minimal triangulation of dunce hat.

 == add

  ASIMP ==> FiniteSimplicialComplex(VS)

  -- generate a simplicial complex equivalent to a solid hyper-sphere.
  -- That is, a solid volume with no holes in it, so we use a single
  -- n-dimensional simplex.
  sphereSolid(dim : NNI) : ASIMP ==
      v1 : List(List(NNI)) := [[n for n in 1..(dim + 1)]]
      vs1 : List(VS) := []
      simplicialComplex(vs1, v1)$ASIMP

  -- generate a simplicial complex equivalent to the surface of a
  -- hyper-sphere. That is, the surface of a volume with no holes in
  -- it, so we use a minimal triangulation of the faces of a single
  -- simplex.
  sphereSurface(dim : NNI) : ASIMP ==
      s := sphereSolid(dim)
      boundary(s)

  -- create a simplicial complex containing a line
  line() : ASIMP ==
      v1 : List(List(NNI)) := ([[1, 2]])
      vs1 : List(VS) := []
      simplicialComplex(vs1, v1)$ASIMP

  -- generate a minimal triangulation of the surface of a
  -- 3-dimensional torus.
  torusSurface() : ASIMP ==
      v1 : List(List(NNI)) := ([[1, 2, 3], [2, 3, 5], [2, 4, 5], [2, 4, 7],
         [1, 2, 6], [2, 6, 7], [3, 4, 6], [3, 5, 6], [3, 4, 7], [1, 3, 7],
         [1, 4, 5], [1, 4, 6], [5, 6, 7], [1, 5, 7]])
      vs1 : List(VS) := [] --  7
      simplicialComplex(vs1, v1)$ASIMP

  -- generate a minimal triangulation of the surface of a cylinder
  -- without ends
  band() : ASIMP ==
      v1 : List(List(NNI)) := ([[1, 2, 3], [1, 2, 6], [1, 5, 6],
                               [2, 3, 4], [3, 4, 5], [4, 5, 6]])
      vs1 : List(VS) := [] -- = 6
      simplicialComplex(vs1, v1)$ASIMP

  -- generate a minimal triangulation of the surface of a Moebius band.
  moebiusBand() : ASIMP ==
      v1 : List(List(NNI)) := ([[1, 2, 3], [2, 3, 4], [3, 4, 5],
                               [1, 4, 5], [1, 2, 5]])
      vs1 : List(VS) := [] -- 5
      simplicialComplex(vs1, v1)$ASIMP

  -- generate a minimal triangulation of the surface of a projective plane.
  -- (A punctured projective plane is equivalent to a Moebius band)
  projectiveSpace(n : Integer) : ASIMP ==
      v1 : List(List(NNI)) := ([[1, 2, 3], [1, 3, 4], [1, 2, 6], [1, 5, 6],
         [1, 4, 5], [2, 3, 5], [2, 4, 5], [2, 4, 6], [3, 4, 6], [3, 5, 6]])
      vs1 : List(VS) := [] -- 6
      simplicialComplex(vs1, v1)$ASIMP

  -- generate a minimal triangulation of the surface of a Klein bottle.
  kleinBottle() : ASIMP ==
      v1 : List(List(NNI)) := ([[3, 4, 8], [2, 3, 4], [2, 4, 6], [2, 6, 8],
         [2, 5, 8], [3, 5, 7], [2, 3, 7], [2, 7, 1], [2, 5, 1], [3, 5, 1],
         [4, 5, 8], [4, 5, 7], [4, 6, 7], [6, 7, 1], [3, 6, 1], [3, 6, 8]])
      vs1 : List(VS) := [] -- 8
      simplicialComplex(vs1, v1)$ASIMP

  dunceHat() : ASIMP ==
      v1 : List(List(NNI)) := ([[1, 2, 8], [2, 3, 8], [3, 7, 8], [1 ,3, 7],
         [1, 2, 7], [1, 6, 8], [1, 2, 6], [6, 7, 8], [2, 4, 6], [5, 6, 7],
         [2, 5, 7], [4, 5, 6], [2, 3, 4], [2, 3, 5], [1, 3, 4], [1, 4, 5],
         [1, 3, 5]])
      vs1 : List(VS) := [] -- 8
      simplicialComplex(vs1, v1)$ASIMP

)if false
\section{DeltaComplex Factory}

DeltaComplexFactory constructs example delta complexes for us.

These can be smaller than DeltaComplexes constructed from SimplicialComplexes.
)endif

)abbrev package DELTCF DeltaComplexFactory
++ Author: Martin Baker, Waldek Hebisch
++ Date Created: Sept 2016
++ Description:
++  Provides functions to construct various minimal delta complexes which
++  can be smaller than if constructed from SimplicialComplexes.
PI ==> PositiveInteger
NNI==> NonNegativeInteger
x<<y ==> hconcat(x::OutputForm, y::OutputForm)

DeltaComplexFactory(VS : AbelianGroup) : with
  circle : () -> DeltaComplex(VS)
    ++ Generate a minimal circle from an edge from vertex A to A.
  dunceHat : () -> DeltaComplex(VS)
    ++ generate a minimal dunceHat from 3 edges.
  torusSurface : () -> DeltaComplex(VS)
    ++ generate a minimal 3-dimensional torus surface.
  projectiveSpace : (n : Integer) -> DeltaComplex(VS)
    ++ Generate a minimal projective space.
  kleinBottle : () -> DeltaComplex(VS)
    ++ generate a minimal Klein bottle.

 == add

  DC ==> DeltaComplex(VS)

  -- Generate a minimal circle from an edge from vertex A to A.
  circle() : DC ==
      vs1 : List(VS) := []
      deltaComplex(vs1,1, [[[1, -1]]])$DC

  -- generate a minimal dunceHat from 3 edges.
  dunceHat() : DC ==
      vs1 : List(VS) := []
      deltaComplex(vs1,1, [[[1, 1, -1]],[[1, -1]]])$DC

  -- generate a minimal torus.
  torusSurface() : DC ==
      vs1 : List(VS) := []
      deltaComplex(vs1,1, [[[1, 2, -1, -2]],[[1, -1], [1, -1]]])$DC

  projectiveSpace(n : Integer) : DC ==
      vs1 : List(VS) := []
      cl : List(List(List(Integer))) := []
      for i in 1..n repeat
          cl := cons([[1, (odd?(i) => -1; 1)]], cl)
      deltaComplex(vs1,1,cl)$DC

  -- generate a minimal surface of a Klein bottle.
  kleinBottle() : DC ==
      vs1 : List(VS) := []
      deltaComplex(vs1,1, [[[1, 2, 1, -2]],[[1, -1], [1, -1]]])$DC

)if false
\section{Cubical Complex Factory}
Provides functions to construct various Cubical complexes.
For more information see:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/cubical/factory/}

\begin{itemize}
\item sphereSolid - in any dimension 'dim'
A cubical complex equivalent to a solid hyper-sphere. That is, a solid
volume with no holes in it, so we use a single n-dimensional cube.
\item sphereSurface - in any dimension 'dim'
A cubical complex equivalent to the surface of a hyper-sphere.
That is, the surface of a volume with no holes in it, so we use the faces
of a single cube.
\item torusSurface - A cubical complex of the surface of a
3-dimensional torus.
\item band - A cubical complex of the surface of a cylinder without
ends
\item projectivePlane - A cubical complex of the surface of a
projective plane.
A one sided surface which cannot be embedded in 3D space without
intersecting itself. There are no external boundaries, every edge
connects two faces.
(A punctured projective plane is equivalent to a Moebius band)
\end{itemize}

I would also like to add:
\begin{itemize}
\item moebiusBand - a cubical complex of the surface of a Moebius band.
A one sided surface with a single external boundary line.
\item kleinBottle -  a cubical complex of the surface of a Klein bottle.
\end{itemize}
but I have not yet worked out if, or how, this can be done for a cubical
complex.
)endif

)abbrev package CUBECF CubicalComplexFactory
++ Author: Martin Baker
++ Date Created: Apr 2016
++ Description:
++  Provides functions to construct various Cubical complexes.
++  For more information see:
++  http://www.euclideanspace.com/prog/scratchpad/mycode/topology/cubical/factory/
PI ==> PositiveInteger
NNI==> NonNegativeInteger
x<<y ==> hconcat(x::OutputForm, y::OutputForm)

CubicalComplexFactory() : with
  sphereSolid : (dim : NNI) -> FiniteCubicalComplex(Integer)
    ++ Generate a cubical complex equivalent to a solid hyper-sphere. That
    ++ is, a solid volume with no holes in it, so we use a single n-dimensional
    ++ cube.
  sphereSurface : (dim : NNI) -> FiniteCubicalComplex(Integer)
    ++ Generate a cubical complex equivalent to the surface of a
    ++ hyper-sphere. That is, the surface of a volume with no holes
    ++ in it, so we use the faces of a single cube.
  torusSurface : () -> FiniteCubicalComplex(Integer)
    ++ generate a cubical complex of the surface of an 3-dimensional torus.
  band : () -> FiniteCubicalComplex(Integer)
    ++ Generate a cubical complex of the surface of a cylinder without ends
  moebiusBand : () -> FiniteCubicalComplex(Integer)
    ++ Generate a cubical complex of the surface of a Moebius band.
    ++ A one sided surface with a single external boundary line.
  projectiveSpace : (dim : NNI) -> FiniteCubicalComplex(Integer)
    ++ Generate a cubical complex of the surface of a projective plane.
    ++ A one sided surface which cannot be embedded in 3D space without
    ++ intersecting itself. There are no external boundaries, every edge
    ++ connects two faces.
    ++ (A punctured projective plane is equivalent to a Moebius band)
  kleinBottle : () -> FiniteCubicalComplex(Integer)
    ++ generate a cubical complex of the surface of a Klein bottle.

 == add

  -- generate a cubical complex equivalent to a solid hyper-sphere. That
  -- is, a solid volume with no holes in it, so we use a single
  -- n-dimensional cube.
  sphereSolid(dim : NNI) : FiniteCubicalComplex(Integer) ==
      ASIMP := FiniteCubicalComplex(Integer)
      v1 : List(List(List(Integer))) := [[[1, 2] for n in 1..dim]]
      vs1 : List(Integer) := []
      r := cubicalComplex(vs1, v1)$ASIMP
      r

  -- generate a cubical complex equivalent to the surface of a
  -- hyper-sphere. That is, the surface of a volume with no holes in
  -- it, so we use the faces of a single cube.
  sphereSurface(dim : NNI) : FiniteCubicalComplex(Integer) ==
      s := sphereSolid(dim)
      r := boundary(s)
      r

  -- generate a cubical complex of the surface of an 3-dimensional torus.
  torusSurface() : FiniteCubicalComplex(Integer) ==
      a := sphereSurface(2)
      b := sphereSurface(2)
      product(a, b)

  -- generate a cubical complex of the surface of a cylinder without ends
  band() : FiniteCubicalComplex(Integer) ==
      a := sphereSurface(2)
      ASIMP := FiniteCubicalComplex(Integer)
      v1:List(List(List(Integer))) := [[[1, 2]]]
      vs1 : List(Integer) := []
      b := cubicalComplex(vs1, [v1])$ASIMP
      product(a, b)

  -- generate a cubical complex of the surface of a projective plane.
  -- (A punctured projective plane is equivalent to a Moebius band)
  -- see [13] for details
  projectiveSpace(dim : NNI) : FiniteCubicalComplex(Integer) ==
      ASIMP := FiniteCubicalComplex(Integer)
      if dim ~= 2 then
          error "projectiveSpace only defined for plane"
      -- TODO define for other dimensions
      v1 : List(List(List(Integer))) := ([[[1, 2], [1, 1], [1, 1], [1, 2],
          [1, 1]], [[1, 2], [1, 1], [1, 1], [1, 1], [1, 2]], _
                    [[1, 1], [1, 2], [1, 2], [1, 1], [1, 1]], _
                    [[1, 1], [1, 2], [1, 1], [1, 2], [1, 1]], _
                    [[1, 1], [1, 1], [1, 2], [1, 1], [1, 2]], _
                    [[1, 2], [1, 2], [2, 2], [1, 1], [1, 1]], _
                    [[1, 2], [2, 2], [1, 2], [1, 1], [1, 1]], _
                    [[2, 2], [1, 2], [1, 2], [1, 1], [1, 1]], _
                    [[1, 2], [1, 2], [1, 1], [1, 1], [2, 2]], _
                    [[1, 2], [2, 2], [1, 1], [1, 1], [1, 2]], _
                    [[2, 2], [1, 2], [1, 1], [1, 1], [1, 2]], _
                    [[1, 2], [1, 1], [1, 2], [2, 2], [1, 1]], _
                    [[1, 2], [1, 1], [2, 2], [1, 2], [1, 1]], _
                    [[2, 2], [1, 1], [1, 2], [1, 2], [1, 1]], _
                    [[1, 1], [1, 2], [1, 1], [1, 2], [2, 2]], _
                    [[1, 1], [1, 2], [1, 1], [2, 2], [1, 2]], _
                    [[1, 1], [2, 2], [1, 1], [1, 2], [1, 2]], _
                    [[1, 1], [1, 1], [1, 2], [1, 2], [2, 2]], _
                    [[1, 1], [1, 1], [1, 2], [2, 2], [1, 2]], _
                    [[1, 1], [1, 1], [2, 2], [1, 2], [1, 2]]])
      vs1 : List(Integer) := []
      r := cubicalComplex(vs1, v1)$ASIMP
      --print("projectivePlane=" << r)
      r

--Copyright (c) 2016, Martin J Baker.
--All rights reserved.
--
--Redistribution and use in source and binary forms, with or without
--modification, are permitted provided that the following conditions are
--met:
--
--    - Redistributions of source code must retain the above copyright
--      notice, this list of conditions and the following disclaimer.
--
--    - Redistributions in binary form must reproduce the above copyright
--      notice, this list of conditions and the following disclaimer in
--      the documentation and/or other materials provided with the
--      distribution.
--
--    - Neither the name of Martin J Baker. nor the
--      names of its contributors may be used to endorse or promote products
--      derived from this software without specific prior written permission.
--
--THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
--IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
--TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
--PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
--OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
--EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
--PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
--PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
--LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
--NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
--SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

)if false
\eject
\begin{thebibliography}{99}
For more details see:
[1] Mathematics++ Kantor, Matousek, Samal 2015  ISBN 978-1-4704-2261-5
        Chapter 6 - Topology. Contains a relatively gentle introduction
        to homology.

[2] Graphs, Surfaces and Homology, Peter Giblin 2010 ISBN 987-0-521-15405-5
        Builds up to homology groups via graphs and simplicial complexes.

[3] Wikipedia
\url{https://en.wikipedia.org/wiki/Simplicial_complex}

[4] I have put a fuller explanation of this code here:
\url{http://www.euclideanspace.com/prog/scratchpad/mycode/topology/}

[5] Finite simplicial complexes in Sage
\url{http://doc.sagemath.org/html/en/reference/homology/sage/homology/simplicial_complex.html}

[6] Finite simplicial complexes in NPM
\url{https://www.npmjs.com/package/simplicial-complex}

[7] Simpcomp - a GAP package for working with simplicial complexes
\url{https://code.google.com/p/simpcomp/}

[8] A Macaulay2 package for working with simplicial complexes
\url{http://www.math.uiuc.edu/Macaulay2/doc/Macaulay2-1.8.2/share/doc/Macaulay2/SimplicialComplexes/html}

[9] Hatcher - Algebraic Topology - book also available free online.
\url{https://www.math.cornell.edu/~hatcher/AT/AT.pdf}

[10]Computational Geometry - Algorithms and Applications by
    Mark de Berg, Otfried Cheong, Marc van Kreveld and Mark Overmars
    This book looks at the algorithms from a computer science, rather
    than pure mathematics, point-of-view. So homotopy or homology is
    not mentioned but subjects like Voronoi Diagrams, Delauney
    Triangulations, Convex Hulls and many similar topics are covered.
[11] Computational Homology by Tomasz Kaczynski, Konstantin Mischaikow,
           and Marian Mrozek, Springer-Verlag (2004).

[12] Kenzo
\url{https://www-fourier.ujf-grenoble.fr/~sergerar/Kenzo/}
[13] Construction Techniques for Cubical Complexes
\url{http://www.emis.de/journals/EM/expmath/volumes/13/13.4/Schwartz.pdf}
\end{thebibliography}
\end{document}
)endif
