This is how to call morphgen.

This page began as a snapshot of this complex boundary code and its exposition. We interpolate new function into the OCaml code to build the structure necessary for logical navigation within the body of the complex. This project would add undue hair to the original boundary code which I leave intact. Here are some early thoughts on the job of this modified program. This is the compact form of the program that gets updated as bugs are found.

This OCaml code computes the boundary and other artifacts of an oriented simplical complex. Such a complex partitions some space (manifold) into simplexes called zones here. Some finite set of points, which includes all the vertices of all the zones, is enumerated and this numbering is used to specify the vertices of each constituent zone. Generally a list of integers, int list here, depicts some simplex by enumerating its vertices. We seek a minimal addition of this code to support morphogenesis. This code naturally discovers zones that share a facet and can thus make proper introductions which allow zones to send messages to their neighboring zones.

After morphogenesis finishes each zone holds for each neighbor a value of type ('a nAcc) = {ray : 'a rayp; curl : curlp} which are functions to be called to send a message. The type parameter 'a names the type the value returned from some calls. It is filled in by subsequent modules that care about the details of this type.

morphgen stands in the position of boundary in the simpler version. Routine morphgen has two new parameters nz which it calls once for each zone, and tl which is a parameter that allows for peering into the complex and intervening therin. This call creates the closure that becomes the essence of the zone throughout the subsequent ray tracing. This code is a toy demonstration of the morphogenesis pattern. nz takes the list of global vertex numbers for the new zone and returns a pair of functions called intNb and iZC here. They are like ‘methods on the zone’. intNb is called during morphogenesis once per neighbor. It takes a local neighbor index and a channel for messages to the neighbor. iZC is a function that receives such messages from neighbors. There are two arguments in invocations to this function: the local index of the sender, and the message. Whenever a facet is discovered to be shared by two zones, each zone intNb is called passing the local facet number, and the neighbor’s iZC, curried to identify that sender by neighbor index by which the recipient knows the sender. At the end morphgen returns the boundary as a list. Each facet at the boundary is accompanied with the function pair for the zone at the boundary. In the transformation of the boundary code to scaffold code, some values that were vertex lists denoting facets, are changed to denote a pair (Chook, list) where the list is what went before, Chook is the hook as introduced above but Curried to know the zone number is which local number that the zone knows the facet by. A message delivered to a chook is thus augmented by the facet number by which the recipient learns to which facet the message pertains.

Calls per zone to nz involve global vertex numbers which are typically necessary to produce metric information. Subsequent calls to the hook and Chook involve only local information. This restriction is to minimize the amount of code that is in a position to violate action-at-a-distance principles.

We include in red the type inferred by OCaml for each of the program fragments that we have copied into this explanation. We include in blue a sample invocation of the previous program fragment, in the form of an equality that can be verified by the OCaml REPL.

We use a sort routine that is expanded and expounded here. Also use these routine to support the small test cases that follow the definitions below.

exception Repeated_Vertex
exception Logic_error

let pe vl = match let rec mpa l th = match l with
       [] -> [], []
     | (a, b)::c -> if th >= b then raise Repeated_Vertex;
     match mpa c b with sl, l -> (b::sl, (ref false, a)::l)
     in mpa (sort (fun (_, b) (_, d) -> b < d)
       (let rec numr l n = match l with [] -> [] |
          a::b -> (n, a)::(numr b (n + 1)) in numr vl 0))
      (-1) with svl, spl ->
 let ar = Array.of_list spl in
 let rec dp i p = if i < 0 then p else let next = dp (i-1) in
   match ar.(i) with a, n -> if !a then next (not p) else  
    let rec lop v = match ar.(v) with w, x -> 
      if !w then raise Logic_error; w := true;
      if v = i then () else lop x in lop n;          
      next p
 in dp (Array.length ar - 1) false, svl

pe : int list -> bool * int list

pe [3; 6; 5; 8] = (true, [3; 5; 6; 8])
pe takes a simplex and sorts the vertex numbers and reports parity of the permutation of the sort. It is described in expanded form here.
let sl nz l = let (intNba, iZCa) = nz l in
let rec sli l = match l with [] -> []
  | h::t -> (fun q -> h::q) (sli t) 
and app sll k = match sll with [] -> []
    | h::t -> (h, (intNba k, iZCa k))::(app t (k+1)) in
app (sli l) 0;;
sl :
  ('a list -> (int -> 'b) * (int -> 'c)) ->
  'a list -> ('a list * ('b * 'c)) list
(sl z) produces the list of all sublists of z that omit just one member of z. If x is an n-simplex then (sl x) is a list of its n+1 facets, each of which is an (n−1)-simplex. These facets are accompanied with a hook to the zone. The orientation of these facets alternates as we pass down this list. This accounts for some obscure code in rc.
let rc (nz, zl) = List.fold_left
    (fun acc (o, vl) ->
   (* Do all facets of this zone with facet list fac. *)
   (* Yield a pair of face lists assorted by parity. *)
    match (List.fold_left (fun (od, (even, odd)) face ->
    (not od, if o
      then (odd, face::even)
      else (face::odd, even)))
    (false, acc) (sl nz vl))
    with (o, (l, r)) -> if o then (r, l) else (l, r))
    ([], []) ( pe zl)
rc :
     (int list -> (int -> 'a) * (int -> 'b)) * int list list ->
     (int list * ('a * 'b)) list * (int list * ('a * 'b)) list
rc takes a list of simplexes and considers each facet of each simplex. It accumulates these in two lists assorted by parity. Notice logic to alternate parity of facets of one simplex. Note in the given example of two tetrahedra that share the facet [2; 3; 4], that the computed result includes that shared facet in each list.
let cmp (c, _) (d, _) = compare c d;;
cmp : 'a * 'b -> 'a * 'c -> int
To control sorts of pairs of zone hook and simplex.
let b1 q = match (rc q) with a, b ->
   let ob = List.sort cmp in (ob a, ob b);;
b1 :
     (int list -> (int -> 'a) * (int -> 'b)) * int list list ->
     (int list * ('a * 'b)) list * (int list * ('a * 'b)) list
b1 applies rc to a set of simplexes and sorts the results, better to find matches between them. List.sort cmp produces a sort for simplexes.
let rec elim q tl = match q with
   | a, [] -> a, []
   | [], b -> [], b
   | (a::b as rev), (c::d as obv) -> let w = cmp a c in
     if w < 0 then match elim (b, obv) tl with p, q -> a::p, q
     else if w > 0 then match elim (rev, d) tl with p, q -> p, c::q
     else (match (a, c) with (vl, (intNba, iZCa)), (_, (intNbb, iZCb)) ->
           (try let (tapl, tapr) = List.assoc vl tl in
                 intNba (tapl iZCb); intNbb (tapr iZCa);
            with Not_found -> intNba iZCb; intNbb iZCa); elim (b, d) tl)
elim :
     ('a * (('b -> 'c) * 'd)) list * ('a * (('d -> 'e) * 'b)) list ->
     ('a * (('b -> 'b) * ('d -> 'd))) list ->
     ('a * (('b -> 'c) * 'd)) list * ('a * (('d -> 'e) * 'b)) list
elim takes a pair of sorted simplex lists, and eliminates those that are in both. Notice that the shared facet [2; 3; 4] has been eliminated in the result. Such simplexes are facets between neighboring zones which are properly oriented so as not to belong to the boundary of the complex.
let morphgen nz x tl = match (elim (b1 (nz, x)) tl) with (p, q) -> List.append
    ( (fun (x, y) -> x, (false, y)) p) ( (fun (x, y) -> x, (true, y)) q)
morphgen :
      (int list -> (int -> 'a -> 'b) * (int -> 'a)) -> int list list ->
      (int list * 'a tap2) list ->
      (int list * (bool * (('a -> 'b) * 'a))) list
morphgen combines the previous processing and turns the pair of lists into one list, by flipping a pair of vertices in each member of the first list, thus making the output format into the same as the input.
Grok this. This too, too.