@@ -553,8 +553,71 @@ non-trivial congruence containing the congruence represented by a
553553:returns: Whether or not a non-trivial quotient was found.
554554:rtype: tril
555555 )pbdoc" );
556+
557+ m.def (" todd_coxeter_perform_lookbehind" ,
558+ &todd_coxeter::perform_lookbehind,
559+ py::arg (" tc" ),
560+ R"pbdoc(
561+ :sig=(tc: ToddCoxeter) -> None:
562+ :only-document-once:
563+
564+ Perform a lookbehind.
565+
566+ This function performs a "lookbehind" on the argument *tc* which is
567+ defined as follows. For every node ``n`` in the so-far computed
568+ :any:`WordGraph` (obtained from :any:`ToddCoxeter.current_word_graph`) we
569+ use the current word graph to rewrite the current short-lex least path
570+ from the initial node to ``n``. If this rewritten word is not equal to
571+ the original word, and it also labels a path from the initial node in
572+ the current word graph to a node ``m``, then ``m`` and ``n`` represent the
573+ same congruence class. Thus we may collapse ``m`` and ``n`` (i.e. quotient
574+ the word graph by the least congruence containing the pair ``m`` and
575+ ``n``).
576+
577+ The intended use case for this function is when you have a large word
578+ graph in a partially enumerated :any:`ToddCoxeter` instance, and you
579+ would like to minimise this word graph as far as possible.
580+
581+ For example, if we take the following monoid presentation of B. H.
582+ Neumann for the trivial group:
583+
584+ .. code-block:: python
585+
586+ p = Presentation("abcdef")
587+ p.contains_empty_word(true)
588+ presentation.add_inverse_rules(p, "defabc")
589+ presentation.add_rule(p, "bbdeaecbffdbaeeccefbccefb", "")
590+ presentation.add_rule(p, "ccefbfacddecbffaafdcaafdc", "")
591+ presentation.add_rule(p, "aafdcdbaeefacddbbdeabbdea", "")
592+ tc = ToddCoxeter(congruence_kind.twosided, p)
593+
594+ Then running *tc* will simply grow the underlying word graph until
595+ your computer runs out of memory. The authors of ``libsemigroups`` were
596+ not able to find any combination of the many settings for
597+ :any:`ToddCoxeter` where running *tc* returned an answer. We also tried
598+ with GAP and ACE but neither of these seemed able to return an answer
599+ either. But doing the following:
600+
601+ .. code-block:: python
602+
603+ tc.lookahead_extent(options.lookahead_extent.full)
604+ .lookahead_style(options.lookahead_style.felsch)
605+
606+ tc.run_for(timedelta(seconds=1))
607+ tc.perform_lookahead(True)
608+ todd_coxeter.perform_lookbehind(tc)
609+ tc.run_for(timedelta(seconds=1))
610+ todd_coxeter.perform_lookbehind(tc)
611+ tc.perform_lookahead(True)
612+ tc.number_of_classes() # returns 1
613+
614+ returns the correct answer in about 22 seconds (on a 2024 Macbook Pro M4
615+ Pro).
616+
617+ :param tc: the :any:`ToddCoxeter` instance.
618+ :type tc: ToddCoxeter)pbdoc" );
556619 } // bind_todd_coxeter
557- } // namespace
620+ } // namespace
558621
559622 void init_todd_coxeter (py::module & m) {
560623 bind_todd_coxeter<word_type>(m, " ToddCoxeterWord" );
0 commit comments