////////////////////////////////////////////////////////////////////////////////
// 
// SymmetricExtensionGraph.cc
//
//    produced: 12/01/2020 jr
// 
////////////////////////////////////////////////////////////////////////////////

#include <sstream>
#include <iomanip>
#include <thread>
#include <functional>
#include <atomic>

#include "SymmetricExtensionGraph.hh"

namespace topcom {
  std::mutex SymmetricExtensionGraph::_min_mutex;
  
  SymmetricExtensionGraph::SymmetryWorker::SymmetryWorker(const int                         workerID,
							  const SymmetricExtensionGraph&    seg) :
    _workerID(workerID),
    _callerptr(&seg),
    _partial_triang_to_check(),
    _partial_triang_new_simplex(),
    _work_received(false),
    _work_done(true),
    _worker_stopped(false) {
  }

  SymmetricExtensionGraph::SymmetryWorker::SymmetryWorker(const SymmetryWorker& sw) :
    _workerID(sw._workerID),
    _callerptr(sw._callerptr),
    _partial_triang_to_check(sw._partial_triang_to_check),
    _partial_triang_new_simplex(sw._partial_triang_new_simplex),
    _work_received(false),
    _work_done(true),
    _worker_stopped(false) {
  }

  void SymmetricExtensionGraph::SymmetryWorker::operator()() {
    MessageStreams::debug() << message::lock
			    << message::tab
			    << "worker " << _workerID << " is spawned" << std::endl
			    << message::unlock;
    while (!_worker_stopped) {
      MessageStreams::debug() << message::lock
			      << message::tab
			      << "worker " << _workerID << " waiting ..." << std::endl
			      << message::unlock;
      while (!wake_up()) {
	std::unique_lock<std::mutex> main_lock(_callerptr->_main_mutex);
	_worker_condition.wait(main_lock, [this] { return wake_up(); });
      }

      // wake-up because of new work?
      if (_work_received && !_worker_stopped) {
	MessageStreams::debug() << message::lock
				<< message::tab
				<< "worker " << _workerID << " working ..." << std::endl
				<< message::unlock;

	// the actual work is done here:
	check_lex_decreasing_symmetry();
	// actual work done.
    
	MessageStreams::debug() << message::lock
				<< message::tab
				<< "... worker " << _workerID << " done - notifying master" << std::endl
				<< message::unlock;
	{
	  _work_done = true;
	  _work_received = false;
	  std::lock_guard<std::mutex> main_lock(_callerptr->_main_mutex);
	  --_callerptr->_no_of_busy_threads;
	  if (_callerptr->_threads_done()) {
	    _callerptr->_main_condition.notify_one();
	  }
	}
      }
    }
  }

  void SymmetricExtensionGraph::SymmetryWorker::check_lex_decreasing_symmetry() {

    // some other thread may have found that current_partial_triang is not new:
    if (_callerptr->_is_old_symmetry_class) {
      return;
    }
    if (_callerptr->_confdata.classified_extension_symmetries().worker_symmetryptrs(_workerID).empty()) {
      return;
    }
    const Simplex& min_simp(*_partial_triang_to_check.begin());
    const seg_symmetryptr_iterdata& relevant_symmetryptrs(_callerptr->_confdata.classified_extension_symmetries().worker_relevant_symmetryptrs(_workerID, _partial_triang_new_simplex));
    for (seg_symmetryptr_iterdata::const_iterator iter = relevant_symmetryptrs.begin();
	 iter != relevant_symmetryptrs.end();
	 ++iter) {

      // some other thread may have found that current_partial_triang is not new:
      if (_callerptr->_is_old_symmetry_class) {
	return;
      }
      const Symmetry& g(**iter);
      const size_type min_simp_idx = SimplicialComplex::index_of_simplex(min_simp, _callerptr->_confdata.rank());
      const size_type g_new_simp_idx = SimplicialComplex::index_of_simplex(g.map(_partial_triang_new_simplex), _callerptr->_confdata.rank());
      if (g_new_simp_idx < min_simp_idx) {
	_callerptr->_is_old_symmetry_class = true;
	return;
      }
      
      if (g.lex_decreases(_partial_triang_to_check, _callerptr->_confdata.rank())) {

	// now this thread has found that current_partial_triang is not new:
	_callerptr->_is_old_symmetry_class = true;
	return;
      }
    }
  }

  bool SymmetricExtensionGraph::_old_symmetry_class(const PartialTriang& current_partial_triang,
						    const Simplex&       new_simp) {
    if (CommandlineOptions::parallel_symmetries()) {
      {
	std::lock_guard<std::mutex> main_lock(_main_mutex);
	_is_old_symmetry_class = false;
	_no_of_busy_threads = 0;
      }
      for (int i = 0; i < _no_of_threads; ++i) {
	MessageStreams::debug() << message::lock
				<< message::tab
				<< "activating thread " << i << " ..." << std::endl
				<< message::unlock;
	_symmetry_workers[i].pass_work(current_partial_triang, new_simp);
	MessageStreams::debug() << message::lock
				<< message::tab
				<< "... done" << std::endl
				<< message::unlock;
      }
      MessageStreams::debug() << message::lock
			      << "master is notifying all workers ..." << std::endl
			      << message::unlock;
      SimplicialComplex::start_multithreading();
      for (int i = 0; i < _no_of_threads; ++i) {
	_symmetry_workers[i]._worker_condition.notify_one();
	std::lock_guard<std::mutex> main_lock(_main_mutex);
	++_no_of_busy_threads;
      }
      MessageStreams::debug() << message::lock
			      << "master is locking main mutex to wait for threads to finish ..." << std::endl
			      << message::unlock;
      while (!_threads_done()) {
	std::unique_lock<std::mutex> main_lock(_main_mutex);
	_main_condition.wait(main_lock, [this] { return _threads_done(); } );
      }
      MessageStreams::debug() << message::lock
			      << "workers final result = " << _is_old_symmetry_class << std::endl
			      << message::unlock;
      return _is_old_symmetry_class;
    }
    else {
      if (CommandlineOptions::use_switch_tables()) {
      	return _confdata.extension_switchtable().lex_decreases(current_partial_triang.index_set_pure());
      }
      else if (CommandlineOptions::use_classified_symmetries()) {
	return _confdata.classified_extension_symmetries().lex_decreases_by_relevant_symmetries(current_partial_triang, new_simp);
      }
      else {

	// in this case, we use the naive traversal through all symmetries:
	if (_confdata.has_simpidx_symmetries()) {
	  for (SymmetryGroup::const_iterator iter = _confdata.simpidx_symmetries().begin();
	       iter != _confdata.simpidx_symmetries().end();
	       ++iter) {
	    if (iter->lex_decreases(current_partial_triang.index_set_pure())) {
	      return true;
	    }
	  }
	  return false;	
	}
	else {
	  for (SymmetryGroup::const_iterator iter = _confdata.symmetries().begin();
	       iter != _confdata.symmetries().end();
	       ++iter) {
	    if (iter->lex_decreases(current_partial_triang)) {
	      return true;
	    }
	  }
	  return false;	
	}
      }
    }
  }

  bool SymmetricExtensionGraph::_update_coversimps(const PartialTriang&            current_partial_triang,
						   const Simplex&                  freefacet,
						   seg_coversimps_type&            coversimps,
						   seg_coveradmissibles_type&      coveradmissibles,
						   seg_coversimps_flag_type&       these_coversimps_changed,
						   seg_coveradmissibles_flag_type& these_coveradmissibles_changed,
						   bool&                           no_extension_possible) const {
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << message::tab
			      << "updating coversimps of freefacet "
			      << freefacet << " ..." << std::endl
			      << message::unlock;
    }
    SimplicialComplex common_otherfacets_coveradmissibles(current_partial_triang.admissibles());
    SimplicialComplex otherfreefacet_conflictfree; // default construction before loop saves destructions in each loop
    for (SimplicialComplex::const_iterator offiter = current_partial_triang.freeintfacets().begin();
	 offiter != current_partial_triang.freeintfacets().end();
	 ++offiter) {
      if (*offiter == freefacet) {
	continue;
      }
      const Simplex& other_freefacet(*offiter);
      otherfreefacet_conflictfree = coveradmissibles[other_freefacet]; // assignment instead of construction in loop saves destruction
      otherfreefacet_conflictfree += coversimps[other_freefacet];
      common_otherfacets_coveradmissibles *= otherfreefacet_conflictfree;
      // common_otherfacets_coveradmissibles *= (coveradmissibles[other_freefacet] + coversimps[other_freefacet]);
      if (common_otherfacets_coveradmissibles.empty()) {
	if (CommandlineOptions::debug()) {
	  MessageStreams::debug() << message::lock
				  << "no common coveradmissibles of other free interior facets: no extension possible." << std::endl
				  << message::unlock;
	}
	if (_iter_coversimptighten > _maxiter_coversimptighten) {
	  _maxiter_coversimptighten = _iter_coversimptighten;
	}
	no_extension_possible = true;
	return true;
      }
    }
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << "intersection of coveradmissibles of other free interior facets is "
			      << common_otherfacets_coveradmissibles << std::endl
			      << message::unlock;
    }
    if (common_otherfacets_coveradmissibles.superset(coversimps[freefacet])) {
      if (CommandlineOptions::debug()) {
	MessageStreams::debug() << message::lock
				<< "coversimps " << coversimps[freefacet]
				<< " of freefacet " << freefacet << " not tightened" << std::endl
				<< message::unlock;
      }
      return false;
    }
    coversimps[freefacet] *= common_otherfacets_coveradmissibles;
    if (coversimps[freefacet].empty()) {
      if (CommandlineOptions::debug()) {
	MessageStreams::debug() << message::lock
				<< "no coversimp of " << freefacet
				<< " is in coveradmissibles of all other free interior facets: no extension possible." << std::endl
				<< message::unlock;
      }
      if (_iter_coversimptighten > _maxiter_coversimptighten) {
	_maxiter_coversimptighten = _iter_coversimptighten;
      }
      no_extension_possible = true;
      return true;
    }
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << "continue with updated coversimps of " << freefacet << std::endl
			      << message::unlock;
    }
    return true;
  }

  bool SymmetricExtensionGraph::_update_coveradmissibles(const PartialTriang&            current_partial_triang,
							 const Simplex&                  freefacet,
							 seg_coversimps_type&            coversimps,
							 seg_coveradmissibles_type&      coveradmissibles,
							 seg_coversimps_flag_type&       these_coversimps_changed,
							 seg_coveradmissibles_flag_type& these_coveradmissibles_changed) const {
    these_coveradmissibles_changed[freefacet] = false;
    if (!these_coversimps_changed[freefacet]) {
      return false;
    }
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << "updating coveradmissibles of free interior facet " << freefacet << " ..." << std::endl
			      << message::unlock;
    }
    coveradmissibles[freefacet].clear();
    for (SimplicialComplex::const_iterator coveriter = coversimps[freefacet].begin();
	 coveriter != coversimps[freefacet].end();
	 ++coveriter) {
#ifdef TOPCOM_CONTAINERS
      coveradmissibles[freefacet] += _confdata.admtable().operator[](*coveriter);
#else      
      coveradmissibles[freefacet] += _confdata.admtable().find(*coveriter)->second;
#endif
    }
    return true;
  }

  bool SymmetricExtensionGraph::_some_possiblecoversimps_empty(const PartialTriang&            current_partial_triang,
							       seg_coversimps_type&            coversimps,
							       seg_coveradmissibles_type&      coveradmissibles,
							       seg_coversimps_flag_type&       these_coversimps_changed,
							       seg_coveradmissibles_flag_type& these_coveradmissibles_changed) const {
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << "try to tighten coversimplices of free interior facets "
			      << current_partial_triang.freeintfacets() << " ..." << std::endl
			      << message::unlock;
    }
    bool some_coversimps_changed(true);
    _iter_coversimptighten = 0;
    while (some_coversimps_changed) {
      ++_iter_coversimptighten;
      for (SimplicialComplex::const_iterator ffiter = current_partial_triang.freeintfacets().begin();
	   ffiter != current_partial_triang.freeintfacets().end();
	   ++ffiter) {
	const Simplex& freefacet(*ffiter);
	these_coveradmissibles_changed[freefacet] =_update_coveradmissibles(current_partial_triang,
									    freefacet,
									    coversimps,
									    coveradmissibles,
									    these_coversimps_changed,
									    these_coveradmissibles_changed);
      }
#ifdef USE_CONFFACETS    
      for (facets_data::const_iterator ffiter = current_partial_triang.freeconffacets().begin();
      	   ffiter != current_partial_triang.freeconffacets().end();
      	   ++ffiter) {
      	const Simplex& freefacet(*ffiter);
      	these_coveradmissibles_changed[freefacet] =_update_coveradmissibles(current_partial_triang,
      									    freefacet,
      									    coversimps,
      									    coveradmissibles,
      									    these_coversimps_changed,
      									    these_coveradmissibles_changed);
      }
#endif
      some_coversimps_changed = false;
      bool no_extension_possible = false;
      for (SimplicialComplex::const_iterator ffiter = current_partial_triang.freeintfacets().begin();
	   ffiter != current_partial_triang.freeintfacets().end();
	   ++ffiter) {
	const Simplex& freefacet(*ffiter);
	these_coversimps_changed[freefacet] = _update_coversimps(current_partial_triang,
								 freefacet,
								 coversimps,
								 coveradmissibles,
								 these_coversimps_changed,
								 these_coveradmissibles_changed,
								 no_extension_possible);
	if (no_extension_possible) {
	  return true;
	}
	if (these_coversimps_changed[freefacet]) {
	  some_coversimps_changed = true;
	}
      }
#ifdef USE_CONFFACETS    
      for (facets_data::const_iterator ffiter = current_partial_triang.freeconffacets().begin();
      	   ffiter != current_partial_triang.freeconffacets().end();
      	   ++ffiter) {
      	const Simplex& freefacet(*ffiter);
      	these_coversimps_changed[freefacet] = _update_coversimps(current_partial_triang,
      								 freefacet,
      								 coversimps,
      								 coveradmissibles,
      								 these_coversimps_changed,
      								 these_coveradmissibles_changed,
      								 no_extension_possible);
      	if (no_extension_possible) {
      	  return true;
      	}
      	if (these_coversimps_changed[freefacet]) {
      	  some_coversimps_changed = true;
      	}
      }
#endif
    }
    MessageStreams::debug() << message::lock
			    <<  "... no further tightening possible" << std::endl
			    << message::unlock;
    return false;
  }

  bool SymmetricExtensionGraph::_some_possibleinitcoversimps_empty(const PartialTriang&            current_partial_triang,
								   seg_coversimps_type&            coversimps,
								   seg_coveradmissibles_type&      coveradmissibles,
								   seg_coversimps_flag_type&       these_coversimps_changed,
								   seg_coveradmissibles_flag_type& these_coveradmissibles_changed) const {
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << "generating all coversimplices of free interior facets for current_partial_triang "
			      << current_partial_triang << " ..." << std::endl
			      << message::unlock;
    }
    for (SimplicialComplex::const_iterator ffiter = current_partial_triang.freeintfacets().begin();
	 ffiter != current_partial_triang.freeintfacets().end();
	 ++ffiter) {
      const Simplex& freeintfacet(*ffiter);
      const seg_coversimps_type::iterator coversimp_iter
	= coversimps.insert(std::pair<Simplex, SimplicialComplex>(freeintfacet,
								  _confdata.inctable().intcofacets(freeintfacet)
								  * current_partial_triang.admissibles()
								  )
			    ).first;
      if (coversimp_iter->second.empty()) {
	if (CommandlineOptions::debug()) {
	  MessageStreams::debug() << message::lock
				  << "coversimplices of free interior facet " << freeintfacet
				  << " empty: no extension possible" << std::endl
				  << message::unlock;
	}
	return true;
      }
      these_coversimps_changed[freeintfacet] = true;
    }
#ifdef USE_CONFFACETS    
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << "generating all coversimplices of free configuration facets "
			      << current_partial_triang.freeconffacets()
			      << " for current_partial_triang "
			      << current_partial_triang << " ..." << std::endl
			      << message::unlock;
    }
    for (facets_data::const_iterator ffiter = current_partial_triang.freeconffacets().begin();
    	 ffiter != current_partial_triang.freeconffacets().end();
    	 ++ffiter) {
      const Simplex& freeconffacet(*ffiter);
      const seg_coversimps_type::iterator coversimp_iter
    	= coversimps.insert(std::pair<Simplex, SimplicialComplex>(freeconffacet,
    								  _confdata.inctable().confcofacets(freeconffacet)
    								  * current_partial_triang.admissibles()
    								  )
    			    ).first;
      if (coversimp_iter->second.empty()) {
	if (CommandlineOptions::debug()) {
	  MessageStreams::debug() << message::lock
				  << "coversimplices of free configuration facet " << freeconffacet
				  << " empty: no extension possible" << std::endl
				  << message::unlock;
	}
    	return true;
      }
      these_coversimps_changed[freeconffacet] = true;
    }
#endif
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << "... done - no obstruction so far" << std::endl
			      << message::unlock;
    }
    return false;
  }

  bool SymmetricExtensionGraph::_noncoverable_freefacet_lex(const PartialTriang& current_partial_triang) const {

    // we have to check beforehand that neither admissibles nor freefacets are empty:
    if (current_partial_triang.freeintfacets().empty()) {

      // no free facet:
      return false;
    }
    if (current_partial_triang.admissibles().empty()) {

      // no covering simplex:
      return true;
    }
    const Simplex min_coverable = current_partial_triang.admissibles().begin()->lexmin_subset(_confdata.rank() - 1);
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << *current_partial_triang.freeintfacets().begin()
			      << " < "
			      << min_coverable
			      << "?" << std::endl
			      << message::unlock;
    }
    if (current_partial_triang.freeintfacets().begin()->lexsmaller(min_coverable)) {
      if (CommandlineOptions::debug()) {
	MessageStreams::debug() << message::lock
				<< *current_partial_triang.freeintfacets().begin()
				<< " < "
				<< min_coverable << std::endl
				<< message::unlock;
      }
      return true;
    }
    else {
      if (CommandlineOptions::debug()) {
	MessageStreams::debug() << message::lock
				<< *current_partial_triang.freeintfacets().begin()
				<< " not < "
				<< min_coverable << std::endl
				<< message::unlock;
      }
      return false;
    }
  }

  bool SymmetricExtensionGraph::_noncoverable_freefacet_lean(const PartialTriang& current_partial_triang) const {
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << "lean search for a non coverable interior facet in "
			      << current_partial_triang
			      << " ..." << std::endl
			      << message::unlock;
    }
    for (SimplicialComplex::const_iterator ffiter = current_partial_triang.freeintfacets().begin();
	 ffiter != current_partial_triang.freeintfacets().end();
	 ++ffiter) {
      const Simplex& freeintfacet(*ffiter);
      if ((_confdata.inctable().intcofacets(freeintfacet) * current_partial_triang.admissibles()).empty()) {
	if (CommandlineOptions::debug()) {
	  MessageStreams::debug() << message::lock
				  << "coversimplices of free interior facet "
				  << freeintfacet
				  << " empty: no extension possible" << std::endl
				  << message::unlock;
	}
	return true;
      }
    }
#ifdef USE_CONFFACETS    
    for (facets_data::const_iterator ffiter = current_partial_triang.freeconffacets().begin();
    	 ffiter != current_partial_triang.freeconffacets().end();
    	 ++ffiter) {
      const Simplex& freeconffacet(*ffiter);
      if ((_confdata.inctable().confcofacets(freeconffacet) * current_partial_triang.admissibles()).empty()) {
    	MessageStreams::debug() << message::lock
    				<< "coversimplices of free configuration facet "
    				<< freeconffacet
    				<< " empty: no extension possible" << std::endl
    				<< message::unlock;
    	return true;
      }
    }
#endif
    MessageStreams::debug() << message::lock
			    << "... done - no obstruction so far" << std::endl
			    << message::unlock;
    return false;
  }

  bool SymmetricExtensionGraph::_noncoverable_freefacet_strong(const PartialTriang& current_partial_triang) const {
    if (CommandlineOptions::debug()) {
      MessageStreams::debug() << message::lock
			      << "strong search for a non coverable interior facet in "
			      << current_partial_triang
			      << " ..." << std::endl
			      << message::unlock;
    }
    bool result(false);
    seg_coversimps_type coversimps;
    seg_coveradmissibles_type coveradmissibles;
    seg_coversimps_flag_type these_coversimps_changed;
    seg_coveradmissibles_flag_type these_coveradmissibles_changed;

    if (_some_possibleinitcoversimps_empty(current_partial_triang,
					   coversimps,
					   coveradmissibles,
					   these_coversimps_changed,
					   these_coveradmissibles_changed)) {
      if (CommandlineOptions::debug()) {
	MessageStreams::debug() << message::lock
				<< "*** immediate obstruction search successful - no extension possible ***" << std::endl
				<< message::unlock;
      }
      return true;
    }
    if (_some_possiblecoversimps_empty(current_partial_triang,
				       coversimps,
				       coveradmissibles,
				       these_coversimps_changed,
				       these_coveradmissibles_changed)) {
      if (CommandlineOptions::debug()) {
	MessageStreams::debug() << message::lock
				<< "*** extended obstruction search successful - no extension possible ***" << std::endl
				<< message::unlock;
      }
      return true;
    }
    return false;
  }
  
  bool SymmetricExtensionGraph::_noncoverable_freefacet(const PartialTriang& current_partial_triang) const {
    if (CommandlineOptions::no_extension_check()) {
      return false;
    }
    if (CommandlineOptions::full_extension_check()) {
      return this->_noncoverable_freefacet_strong(current_partial_triang);
    }
    else if (CommandlineOptions::use_volume_order() || CommandlineOptions::use_random_order()) {
      return this->_noncoverable_freefacet_lean(current_partial_triang);
    }
    else {
      return this->_noncoverable_freefacet_lex(current_partial_triang);
    }
  }
  
  bool SymmetricExtensionGraph::_not_enough_volume(const PartialTriang& current_partial_triang) const {
    if (CommandlineOptions::symmetries_are_isometric() || _confdata.symmetries().empty()) {
      Field missing_volume = _confdata.volume() - current_partial_triang.covered_volume();
      SimplicialComplex::const_iterator iter = current_partial_triang.admissibles().begin();
      MessageStreams::debug() << message::lock
			      << "still "
			      << current_partial_triang.admissibles().card()
			      << " simplices available to cover an uncovered volume of "
			      << missing_volume
			      << " ..." << std::endl
				<< message::unlock;
      while (missing_volume > FieldConstants::ZERO) {
	if (iter == current_partial_triang.admissibles().end()) {
	  return true;
	}
	missing_volume -= _confdata.voltable().find(*iter)->second;
	++iter;
	MessageStreams::debug() << message::lock
				<< "still "
				<< current_partial_triang.admissibles().card()
				<< " simplices available to cover an uncovered volume of "
				<< missing_volume
				<< " ..." << std::endl
				<< message::unlock;
      }
      return false;
    }
    else {
      return false;
    }
  }
  
  size_type SymmetricExtensionGraph::_min_no_of_missing_simplices(const PartialTriang& current_partial_triang) const {
    const size_type card_bound =  (current_partial_triang.freeintfacets().card()
#ifdef USE_CONFFACETS    
				   +
				   current_partial_triang.freeconffacets().size()
#endif
				   + _confdata.rank() - 1) / _confdata.rank();
    if (CommandlineOptions::use_volume_order() && CommandlineOptions::use_volumes()) {
      
      // in this case, we can rely on current_partial_triang.admissibles() to be sorted by volume:
      Field missing_volume = _confdata.volume();
      missing_volume -= current_partial_triang.covered_volume();
      size_type result = 0UL;
      SimplicialComplex::const_iterator iter = current_partial_triang.admissibles().begin();
      while (missing_volume > FieldConstants::ZERO) {
	if (iter == current_partial_triang.admissibles().end()) {
	  return std::numeric_limits<size_type>::max();
	}
	missing_volume -= _confdata.voltable().find(*iter)->second;
	++result;
	++iter;
      }
      return std::max<size_type>(result, card_bound);
    }
    else {
      return card_bound;
    }
  }
  
  void SymmetricExtensionGraph::_symcount_unconditioned(const PartialTriang& partial_triang,
							const size_type stabilizer_card) {
    if (this->_options.find_min()) {
      const size_type card = partial_triang.card();
      std::lock_guard<std::mutex> min_lock(_min_mutex);
      if (card < this->_mincard) {
	MessageStreams::debug() << message::lock
				<< "found smaller triangulation " << partial_triang
				<< " with " << card << " simplices" << std::endl
				<< message::unlock;
	this->_mintriang = partial_triang;
	this->_mincard = card;
      }
    }
    TriangResult triangresult(_confdata,
			      this->_symcount,
			      this->_ID,
			      this->_runID,
			      0,
			      partial_triang.simplicial_complex());
    if (CommandlineOptions::output_gkzvecs()) {
      triangresult.make_gkzvec();
    }
    _triang_outputter.print_triang(_output_stream, triangresult);
    output_results(MessageStreams::result());
    if (!CommandlineOptions::skip_orbitcount()) {
      
      // use the stabilizer formula:
      if (CommandlineOptions::use_switch_tables()) {
	this->_orbitsize = _confdata.extension_switchtable().orbit_size(partial_triang.index_set_pure());
      }
      else if (CommandlineOptions::use_classified_symmetries() || CommandlineOptions::use_naive_symmetries()) {
	if (!_confdata.has_simpidx_symmetries()) {
	  this->_orbitsize =
	    (_confdata.valid_symmetries().size() + 1)
	    / (_confdata.valid_symmetries().stabilizer_card(partial_triang) + 1);
	}
	else {
	  this->_orbitsize =
	    (_confdata.simpidx_symmetries().size() + 1)
	    / (_confdata.simpidx_symmetries().stabilizer_card(partial_triang.index_set_pure()) + 1);
	}
      }
      else {
	if (!_confdata.has_simpidx_symmetries()) {
	  this->_orbitsize =
	    (_confdata.valid_symmetries().size() + 1)
	    / (stabilizer_card + 1);
	}
	else {
	  this->_orbitsize =
	    (_confdata.simpidx_symmetries().size() + 1)
	    / (stabilizer_card + 1);
	}
      }
    }
    
    // increase the count for symmetry classes:
    ++this->_symcount;
    this->_totalcount += this->_orbitsize;
  }
  
  bool SymmetricExtensionGraph::_meets_regularity_criterion(const SimplicialComplex& triang,
							    bool                     is_regular) {
    if (CommandlineOptions::check_regular()) {
      return is_regular;
    }
    else if (CommandlineOptions::check_nonregular()) {
      return !is_regular;
    }
    else if (CommandlineOptions::check_nonsubregular()) {
      if (is_regular) {

	// triang itself is regular, thus not non-subregular:
	return false;
      }
      else {
	Options downstream_options(_options);
	downstream_options.add_options(Options::IS_DOWNSTREAM_CALL);
	SymmetricFlipGraphAlgorithms sfg_algs(_ID, _confdata, downstream_options, _global_triangcache, _local_triangcache);
	bool regulars_are_reachable = sfg_algs.find_regularcomp_by_gkz_increasing_flips(triang, DIR_UP);
	if (!regulars_are_reachable && CommandlineOptions::check_nonsupregular()) {
	  
	  // in this case, check also for GKZ decreasing flip connectivity:
	  regulars_are_reachable = sfg_algs.find_regularcomp_by_gkz_increasing_flips(triang, DIR_DOWN);
	}
	return !regulars_are_reachable;
      }
    }
    else if (CommandlineOptions::check_nonsupregular()) {
      if (is_regular) {

	// triang itself is regular, thus not non-subregular:
	return false;
      }
      else {
	Options downstream_options(_options);
	downstream_options.add_options(Options::IS_DOWNSTREAM_CALL);
	SymmetricFlipGraphAlgorithms sfg_algs(_ID, _confdata, downstream_options, _global_triangcache, _local_triangcache);
	bool regulars_are_reachable = sfg_algs.find_regularcomp_by_gkz_increasing_flips(triang, DIR_DOWN);
	return !regulars_are_reachable;
      }
    }
    else if (CommandlineOptions::check_possibly_nonregular_comp()) {

      // for this option, we need to explore the flip-graph component of triang:
      if (is_regular) {

	// triang itself is regular (never true for chirotope input), thus invalid:
	return false;
      }
      else {
	Options downstream_options(_options);
	downstream_options.add_options(Options::IS_DOWNSTREAM_CALL);
	SymmetricFlipGraphAlgorithms sfg_algs(_ID, _confdata, downstream_options, _global_triangcache, _local_triangcache);
	bool regulars_are_reachable = sfg_algs.find_regular_by_heuristic_flips(triang);
	return !regulars_are_reachable;
      }
    }
    else if (CommandlineOptions::check_nonregular_comp()) {

      // for this option, we need to explore the whole flip-graph component of triang:
      if (is_regular) {

	// triang itself is regular, thus invalid:
	return false;
      }
      else {
	Options downstream_options(_options);
	downstream_options.add_options(Options::IS_DOWNSTREAM_CALL);
	SymmetricFlipGraphAlgorithms sfg_algs(_ID, _confdata, downstream_options, _global_triangcache, _local_triangcache);
	bool regulars_are_reachable = sfg_algs.find_regular_by_flips(triang);
	return !regulars_are_reachable;
      }
    }
    else {
      return true;
    }
  }

  void SymmetricExtensionGraph::_symcount_according_to_regularity_criterion(const PartialTriang& partial_triang,
									    const size_type stabilizer_card) {
    bool is_regular = false;
    Vector heights;
    if (_confdata.has_points()) {
      RegularityCheck regchecker(_confdata, partial_triang);
      is_regular = regchecker.is_regular(); // changes heights
      heights = regchecker.heights();
    }

    // for chiro input data, all triangulations are candidates for the non-regular component:
    if (_meets_regularity_criterion(partial_triang.simplicial_complex(), is_regular)) {
      ++this->_symcount;
      {
	const size_type card = partial_triang.card();
	std::lock_guard<std::mutex> min_lock(_min_mutex);
	if (card < this->_mincard) {
	  this->_mintriang = partial_triang;
	  this->_mincard = card;
	}
      }
      TriangResult triangresult(_confdata,
				this->_symcount,
				this->_ID,
				this->_runID,
				0,
				partial_triang.simplicial_complex());
      triangresult.set_is_regular(is_regular);
      if (is_regular && CommandlineOptions::output_heights()) {
	triangresult.set_heights(heights);
      }
      if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	triangresult.make_gkzvec();
      }
      if (CommandlineOptions::check_unimodular() && _confdata.has_points()) {
	triangresult.set_is_unimodular(true);
      }
      if (CommandlineOptions::observe_required_symmetries()) {
	triangresult.set_has_required_syms(true);
      }
      _triang_outputter.print_triang(_output_stream, triangresult);
      output_results(MessageStreams::result());
      if (!CommandlineOptions::skip_orbitcount()) {
	
	// use the stabilizer formula:
	if (CommandlineOptions::use_switch_tables()) {
	  this->_orbitsize = _confdata.extension_switchtable().orbit_size(partial_triang.index_set_pure());
	}
	else if (CommandlineOptions::use_classified_symmetries() || CommandlineOptions::use_naive_symmetries()) {
	  if (!_confdata.has_simpidx_symmetries()) {
	    this->_orbitsize =
	      (_confdata.valid_symmetries().size() + 1)
	      / (_confdata.valid_symmetries().stabilizer_card(partial_triang) + 1);
	  }
	  else {
	    this->_orbitsize =
	      (_confdata.simpidx_symmetries().size() + 1)
	      / (_confdata.simpidx_symmetries().stabilizer_card(partial_triang.index_set_pure()) + 1);
	  }
	}
	else {
	  if (!_confdata.has_simpidx_symmetries()) {
	    this->_orbitsize =
	      (_confdata.valid_symmetries().size() + 1)
	      / (stabilizer_card + 1);
	  }
	  else {
	    this->_orbitsize =
	      (_confdata.simpidx_symmetries().size() + 1)
	      / (stabilizer_card + 1);
	  }
	}
      }
    }
    else { 
      MessageStreams::debug() << message::lock
			      << "regularity criterion violated: " << partial_triang.simplicial_complex() << std::endl
			      << message::unlock;
    }
  }

  void SymmetricExtensionGraph::_orbitcount_according_to_regularity_criterion(const PartialTriang& partial_triang) {
    RegularityCheck regchecker(_confdata, partial_triang);
    const bool is_regular = regchecker.is_regular(); // changes heights
    if (_meets_regularity_criterion(partial_triang, is_regular)) {
      _orbit.insert(partial_triang);
      _regularity_dependent_orbit.insert(partial_triang);
      TriangResult triangresult(_confdata,
				this->_symcount,
				this->_ID,
				this->_runID,
				0,
				partial_triang.simplicial_complex());
      triangresult.set_is_regular(is_regular);
      if (is_regular && CommandlineOptions::output_heights()) {
	triangresult.set_heights(regchecker.heights());
      }
      if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	triangresult.make_gkzvec();
      }
      if (CommandlineOptions::check_unimodular() && _confdata.has_points()) {
	triangresult.set_is_unimodular(true);
      }
      if (CommandlineOptions::observe_required_symmetries()) {
	triangresult.set_has_required_syms(true);
      }
      {
	const size_type card = partial_triang.card();
	std::lock_guard<std::mutex> min_lock(_min_mutex);
	if (card < this->_mincard) {
	  this->_mintriang = partial_triang;
	  this->_mincard = card;
	}
      }
      _triang_outputter.print_triang(_output_stream, triangresult);
      output_results(MessageStreams::result());
    }
    else {
      MessageStreams::debug() << message::lock
			      << "regularity criterion violated: " << partial_triang.simplicial_complex() << std::endl
			      << message::unlock;
    }
    if (!CommandlineOptions::skip_orbitcount()) {
      for (SymmetryGroup::const_iterator symiter = _confdata.valid_symmetries().begin();
	   symiter != _confdata.valid_symmetries().end();
	   ++symiter) {
	const Symmetry& g(*symiter);
	const pt_complex_type equivalent_triang(g.map(partial_triang));
	if (_orbit.find(equivalent_triang) == _orbit.end()) {
	  
	  // not yet processed element in orbit:
	  _orbit.insert(equivalent_triang);
	  RegularityCheck orbregchecker(_confdata, equivalent_triang);
	  const bool is_regular = orbregchecker.is_regular(); // changes heights
	  if (_meets_regularity_criterion(equivalent_triang, is_regular)) {
	    {
	      const size_type card = equivalent_triang.card();
	      std::lock_guard<std::mutex> min_lock(_min_mutex);
	      if (card < this->_mincard) {
		this->_mintriang = equivalent_triang;
		this->_mincard = card;
	      }
	    }
	    TriangResult equivalent_triangresult(_confdata,
						 this->_symcount,
						 this->_ID,
						 this->_runID,
						 this->_regularity_dependent_orbit.size(),
						 equivalent_triang);
	    equivalent_triangresult.set_is_regular(is_regular);
	    if (is_regular && CommandlineOptions::output_heights()) {
	      equivalent_triangresult.set_heights(regchecker.heights());
	    }
	    if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	      equivalent_triangresult.make_gkzvec();
	    }
	    if (CommandlineOptions::check_unimodular()) {
	      equivalent_triangresult.set_is_unimodular(true);
	    }
	    if (CommandlineOptions::observe_required_symmetries()) {
	      equivalent_triangresult.set_has_required_syms(true);
	    }	    
	    _triang_outputter.print_triang(_output_stream, equivalent_triangresult);
	    this->_regularity_dependent_orbit.insert(equivalent_triang);
	    output_results(MessageStreams::result());		    
	  }
	  else {
	    MessageStreams::debug() << message::lock
				    << "regularity criterion violated in orbit: " << equivalent_triang << std::endl
				    << message::unlock;
	  }
	}
      }
      this->_orbitsize = this->_regularity_dependent_orbit.size();
      if (this->_orbitsize > 0) {
	
	// follow the (arbitrary) convention that a symmetry class is [non]regular
	// iff its orbit contains at least one [non]regular triangulation:
	++this->_symcount;
      }
    }
  }

  void SymmetricExtensionGraph::_init() {
    if (CommandlineOptions::output_asy()) {
      Graphics::run_to_asy(_ID);
      Graphics::partialtriang_to_asy(_ID, _runID, _rootptr->partial_triang().index_set_pure(), _rootptr->partial_triang());
    }
    if (CommandlineOptions::parallel_symmetries()) {
      _no_of_threads = CommandlineOptions::no_of_threads();
      _init_symmetry_workers();
    }
    if (_options.output_triangs()) {
      if (CommandlineOptions::debug()) {
	_triang_outputter.activate_debug();
      }
      else {
	_triang_outputter.activate();
      }
    }
    
    // from here on we need to block IO:
    MessageStreams::debug() << message::lock
			    << "exploring all symmetry classes of triangulations by extension ..." << '\n'
			    << message::unlock;
    MessageStreams::debug() << message::lock
			    << "SymmetricExtensionGraph:" << '\n'
			    << "root triangulation: " << _rootptr->partial_triang() << '\n'
			    << "freeintfacets     : " << _rootptr->partial_triang().freeintfacets() << '\n'
			    << "admissibles       : " << _rootptr->partial_triang().admissibles() << '\n'
			    << "admissibles table : " << _confdata.admtable() << '\n'
			    << "incidences        : " << _confdata.inctable() << std::endl;
    if (_confdata.has_voltable()) {
      MessageStreams::debug() << "volumes           : " << _confdata.voltable() << std::endl;
    }
    MessageStreams::debug() << message::unlock;
 }
  
  void SymmetricExtensionGraph::_init_symmetry_workers() {
    MessageStreams::debug() << message::lock
			    << "initialize distributed symmetry workers ..." << std::endl
			    << message::unlock;
    for (int i = 0; i < _no_of_threads; ++i) {
      MessageStreams::debug() << message::lock
			      << message::tab
			      << "initializing symmetry worker " << i
			      << " ..." << std::endl
			      << message::unlock;
      _symmetry_workers.emplace_back(i, *this);
      MessageStreams::debug() << message::lock
			      << message::tab
			      << "... done" << std::endl
			      << message::unlock;
    }
    for (int i = 0; i < _no_of_threads; ++i) {
      MessageStreams::debug() << message::lock
			      << message::tab
			      << "starting symmetry worker thread " << i 
			      << " ..." << std::endl
			      << message::unlock;
      _threads.push_back(std::thread(&SymmetricExtensionGraph::SymmetryWorker::operator(),
				     std::ref(_symmetry_workers[i])));
      MessageStreams::debug() << message::lock
			      << message::tab
			      << "... done" << std::endl
			      << message::unlock;
    }
    MessageStreams::debug() << message::lock
			    << "... done" << std::endl
			    << message::unlock;
  }

  void SymmetricExtensionGraph::_process_node(const node_type& current_node) {
    SimplicialComplex forbidden;
    const PartialTriang& current_partial_triang(current_node.partial_triang());    
    MessageStreams::debug() << message::lock
			    << "extending the following partial triangulation:" << '\n'
			    << "partial triangulation: " << current_partial_triang << '\n'
			    << "freeintfacets        : " << current_partial_triang.freeintfacets() << '\n'
			    << "admissibles          : " << current_partial_triang.admissibles() << '\n'
			    << message::unlock;
    const size_type workload(current_partial_triang.admissibles().card());  
    size_type doneload(0UL);
    const size_type next_card = current_partial_triang.card() + 1;
    const size_type depth = next_card;
    MessageStreams::debug() << message::lock
			    << "resizing progress vector to " << depth << " ..." << std::endl
			    << message::unlock;
    _progress_vector.resize(depth);
    MessageStreams::debug() << message::lock
			    << "D|W|I"
#ifdef USE_CONFFACETS
			    << "|C -> "
#endif
			    << std::setw(4) << depth << "|"
			    << std::setw(4) << workload << "|"
			    << std::setw(4) << current_partial_triang.freeintfacets().card() << "|"
#ifdef USE_CONFFACETS    
			    << std::setw(4) << current_partial_triang.freeconffacets().size()
#endif
			    << std::endl
			    << message::unlock;
      
    // for asy output:
    size_type parent_node_ID = this->_nodecount;
      
    // loop over all admissible extensions:
    for (SimplicialComplex::const_iterator simpiter = current_partial_triang.admissibles().begin();
	 simpiter != current_partial_triang.admissibles().end();
	 ++simpiter) {
      ++doneload;
      _progress_vector.at(depth - 1) = (100 * doneload) / workload;
      ++this->_nodecount;
      const Simplex& new_simp(*simpiter);
	
      // for asy output:
      size_type child_node_ID = this->_nodecount;
	
      if (CommandlineOptions::output_asy()) {
	SimplicialComplex next_partial_triang(current_partial_triang + new_simp);
	Graphics::partialtriang_to_asy(_ID, _runID, next_partial_triang.index_set_pure(), next_partial_triang);
	Graphics::arc_to_asy(_ID, _runID, parent_node_ID, child_node_ID);
      }      
      report_progress(MessageStreams::verbose());
      if (!CommandlineOptions::no_extension_check()
	  && !CommandlineOptions::use_volume_order()
	  && !CommandlineOptions::use_random_order()) {
	  
	// in this lex-order case, we can check whether the new simplex is able to cover the minimal uncovered facets;
	// if not, neither this new simplex nor the upcoming ones will cover it:
	const Simplex min_coverable = new_simp.lexmin_subset(_confdata.rank() - 1);
	if (!current_partial_triang.freeintfacets().empty() && current_partial_triang.freeintfacets().begin()->lexsmaller(min_coverable)) {
	  if (CommandlineOptions::debug()) {
	    MessageStreams::debug() << message::lock
				    << *current_partial_triang.freeintfacets().begin()
				    << " < "
				    << min_coverable << std::endl
				    << message::unlock;
	  }
	    
	  // for asy output:
	  if (CommandlineOptions::output_asy()) {
	    Graphics::veryearlydeadend_to_asy(_ID, _runID, child_node_ID);
	  }
	    
	  ++this->_earlydeadendcount;
	  break;
	}
      }
      forbidden += new_simp;
      MessageStreams::debug() << message::lock
			      << "adding " << new_simp << " to "
			      << current_partial_triang << " ..." << '\n'
			      << "node " << this->_nodecount << ": checking new partial triangulation:" << '\n'
			      << "current free facets" << '\n'
			      << current_partial_triang.freeintfacets() << std::endl
			      << message::unlock;
	
      // construct the extended partial triangulation:
      PartialTriang next_partial_triang(_confdata, current_partial_triang, new_simp, forbidden);
      MessageStreams::debug() << message::lock
			      << "resulting in " << next_partial_triang << '\n'
			      << next_partial_triang << '\n'
			      << "next free facets" << '\n'
			      << next_partial_triang.freeintfacets() << std::endl
			      << message::unlock;
	
      // check lower bound for minimality if required:
      if (this->_options.find_min()) {
	size_type min_no_of_simplices = depth + this->_min_no_of_missing_simplices(next_partial_triang);
	std::lock_guard<std::mutex> min_lock(_min_mutex);
	if (min_no_of_simplices >= this->_mincard) {
	  MessageStreams::debug() << message::lock
				  << "current minimal cardinality is " << this->_mincard << '\n'
				  << "minimal no. of simplices is    " << min_no_of_simplices << '\n'
				  << " => prune" << std::endl
				  << message::unlock;
	  continue;
	}
      }
	
      // check target no of simplices if required:
      size_type target_no_of_simplices = 0;
      if (CommandlineOptions::max_no_of_simplices() > 0) {
	target_no_of_simplices = CommandlineOptions::max_no_of_simplices();
      }
      if (CommandlineOptions::no_of_simplices() > 0) {
	target_no_of_simplices = CommandlineOptions::no_of_simplices();
      }
      if (target_no_of_simplices > 0) {
	size_type min_no_of_simplices = depth + this->_min_no_of_missing_simplices(next_partial_triang);
	if (min_no_of_simplices > target_no_of_simplices) {
	  MessageStreams::debug() << message::lock
				  << "current no. of simplices = " << depth << '\n'
				  << "minimal number of missing simplices = " << min_no_of_simplices << '\n'
				  << "=> target no. of simplices " << target_no_of_simplices << " unreachable => prune" << std::endl
				  << message::unlock;
	  continue;
	}
      }
	
      // since the following check is faster than checking for an old symmetry class,
      // we prepend it here - check whether we are in a dead end:
      if (!next_partial_triang.freeintfacets().empty()) {
	  
	// not yet a triangulation - perform the extendability checks:

	// no simplices left to extend the partial triangulation:
	if (next_partial_triang.admissibles().empty()) {
	    
	  // we are in a dead end:
	  MessageStreams::debug() << message::lock
				  << "freeintfacets: " << next_partial_triang.freeintfacets() << '\n'
				  << "admissibles  : " << next_partial_triang.admissibles() << '\n'
				  << "reached maximal non-triangulation:" << '\n'
				  << next_partial_triang << std::endl
				  << message::unlock;
	    
	  // for asy output:
	  if (CommandlineOptions::output_asy()) {
	    Graphics::deadend_to_asy(_ID, _runID, child_node_ID);
	  }
	    
	  ++this->_deadendcount;
	  continue;
	}

	// the following checks are faster than checking for a lex-smaller partial triangulation,
	// thus, they are performed first:
	// check whether we can extend current_partial_triang at all free facets:
	MessageStreams::debug() << message::lock
				<< "cover obstruction check for "
				<< next_partial_triang << std::endl
				<< message::unlock;
	  
	// the following check is faster than to check for an equivalent partial triangulation:
	// check whether the partial triangulation can possibly be extended:
	if (this->_noncoverable_freefacet(next_partial_triang)) {
	    
	  // early detected dead end:
	  MessageStreams::debug() << message::lock
				  << "cover obstruction hit - no extension possible of "
				  << next_partial_triang << std::endl
				  << message::unlock;
	    
	  // for asy output:
	  if (CommandlineOptions::output_asy()) {
	    Graphics::earlydeadend_to_asy(_ID, _runID, child_node_ID);
	  }
	    
	  ++this->_earlydeadendcount;
	  continue;
	}
	  
	// another fast check:
	// check whether there is still enough volume in the possibly extending simplices:
	if (CommandlineOptions::use_volumes() && !this->_options.find_min()) {
	  MessageStreams::debug() << message::lock
				  << "volume obstruction check for "
				  << next_partial_triang << std::endl
				  << message::unlock;
	  if (this->_not_enough_volume(next_partial_triang)) {
	      
	    // early detected dead end:
	    MessageStreams::debug() << message::lock
				    << "volume obstruction hit - no extension possible of "
				    << next_partial_triang << std::endl
				    << message::unlock;
	      
	    // for asy output:
	    if (CommandlineOptions::output_asy()) {
	      Graphics::missingvolume_to_asy(_ID, _runID, child_node_ID);
	    }
	      
	    ++this->_earlydeadendcount;
	    continue;
	  }
	}
      }
	
      // now we must check whether we really have found something new (also for triangulations):
      MessageStreams::debug() << message::lock
			      << "checking for old symmetry class ..." << std::endl
			      << message::unlock;
      bool is_new = true;
      critical_simpidx_table_type new_critsimp_table;
      size_type new_stabilizer_card = 0UL;
      if (CommandlineOptions::use_classified_symmetries() || CommandlineOptions::use_switch_tables() || CommandlineOptions::use_naive_symmetries()) {
	  
	// use the method of TOPCOM-1.0.0:
	is_new = !_old_symmetry_class(next_partial_triang, new_simp);
      }
      else {
	  
	// use the method of TOPCOM-1.0.1 and above:
	if (!_confdata.has_simpidx_symmetries()) {
	  is_new = current_node.child_is_lexmin_lean(_confdata, new_simp, &new_critsimp_table, &new_stabilizer_card);
	  _seecount += current_node.seecount();
	  current_node.reset_seecount();
	}
	else {
	  is_new = current_node.child_is_lexmin(_confdata, new_simp, &new_critsimp_table, &new_stabilizer_card);
	  _seecount += current_node.seecount();
	  current_node.reset_seecount();
	}
      }
	
      if (!is_new) {
	  
	// for asy output:
	if (CommandlineOptions::output_asy()) {
	  Graphics::notnew_to_asy(_ID, _runID, child_node_ID);
	}	
	MessageStreams::debug() << message::lock
				<< next_partial_triang << " was found already: continue" << std::endl
				<< message::unlock;
	continue;
      }
      else {
	MessageStreams::debug() << message::lock
				<< next_partial_triang << " was not found yet: process" << std::endl
				<< message::unlock;
      }
	
      // next partial triang is new - check for completeness:
      if (next_partial_triang.freeintfacets().empty()) {
	  
	// we found a complete triangulation - check regularity if required and output the triangulation:
	  
	// check lower bound for minimality again using the lower bound computed above:
	if (this->_options.find_min()) {
	  std::lock_guard<std::mutex> min_lock(_min_mutex);
	  if (depth >= this->_mincard) {
	    MessageStreams::debug() << message::lock
				    << "current minimal cardinality is " << this->_mincard << '\n'
				    << "current no. of simplices is    " << depth << '\n'
				    << " => ignore" << std::endl
				    << message::unlock;
	    continue;
	  }
	}
	  
	// check target no of simplices if required:
	if (((CommandlineOptions::no_of_simplices() > 0) && (depth != CommandlineOptions::no_of_simplices()))
	    || ((CommandlineOptions::max_no_of_simplices() > 0) && (depth > CommandlineOptions::max_no_of_simplices()))) {
	  MessageStreams::debug() << message::lock
				  << "current no. of simplices = " << depth << '\n'
				  << "target no. of simplices = " << CommandlineOptions::no_of_simplices() << '\n'
				  << "=> ignore" << std::endl
				  << message::unlock;	
	  continue;
	}
	  
	if (CommandlineOptions::debug()) {
	    
	  // next partial triang is a complete triang –
	  // double-check required symmetries
	  // (should be taken care of by the stripped admissibles):
	  if (!_confdata.required_symmetries().fixes(next_partial_triang)) {
	    MessageStreams::debug() << message::lock
				    << next_partial_triang << " does not have required symmetries - ignore" << std::endl
				    << message::unlock;
	    continue;
	  }
	}
	  
	if (CommandlineOptions::regularity_checks_needed()) {
    
	  // in this branch, regularity is checked and only [non]regular triangulations are counted/output:
	  if (CommandlineOptions::symmetries_are_affine()) {
	      
	    // here we need to check only the found representative for regularity:
	    _symcount_according_to_regularity_criterion(next_partial_triang, new_stabilizer_card);
	  }
	  else {
	      
	    // here we need to check the complete orbit (except the user wants to skip this):
	    _orbitcount_according_to_regularity_criterion(next_partial_triang);
	  }
	    
	  this->_totalcount += this->_orbitsize;
	  this->_orbit.clear();
	  this->_regularity_dependent_orbit.clear();
	}
	else {

	  // no regularity check requested:
	  _symcount_unconditioned(next_partial_triang, new_stabilizer_card);
	}
	  
	// for asy output:
	if (CommandlineOptions::output_asy()) {
	  Graphics::solution_to_asy(_ID, _runID, child_node_ID);
	}		  
	this->_orbitsize = 0;
      }
      else {
	  
	// we can use regularity checks already for partial triangulations;
	// whether or not this is faster depends
	// on the number of non-regular partial triangulations:
	// if there are many, then regularity checks on all
	// partial triangulations may pay off;
	// note that this is only correct if regularity
	// is constant in orbits:
	if (CommandlineOptions::check_regular_early()
	    && CommandlineOptions::check_regular()
	    && CommandlineOptions::symmetries_are_affine()) {
	  RegularityCheck pt_regchecker(_confdata, next_partial_triang);
	  if (!pt_regchecker.is_regular()) {
	    MessageStreams::debug() << message::lock
				    << next_partial_triang << " is already non-regular: continue" << std::endl
				    << message::unlock;
	    continue;
	  }
	}
	_update_continue_work();
	if (_continue_work) {	
	  if (CommandlineOptions::iterative_dfs()) {
	    MessageStreams::debug() << message::lock
				    << "pushing node on stack with " << next_partial_triang << std::endl
				    << message::unlock;
	    this->_nodes_to_explore.emplace(current_node, std::move(next_partial_triang), next_card, std::move(new_critsimp_table));
	  }
	  else {
	    MessageStreams::debug() << message::lock
				    << "entering recursion with " << next_partial_triang << std::endl
				    << message::unlock;
	    this->_dfs(node_type(current_node, std::move(next_partial_triang), next_card, std::move(new_critsimp_table)));
	  }
	}
	else {
	  MessageStreams::debug() << message::lock
				  << "interrupting enumeration of worker " << _ID << " ..." << std::endl
				  << message::unlock;
	  _open_nodes.emplace_back(current_node, std::move(next_partial_triang), next_card, std::move(new_critsimp_table));
	  MessageStreams::debug() << message::lock
				  << "... done" << std::endl
				  << message::unlock;
	}
      }
    }
  }
  
  void SymmetricExtensionGraph::_dfs_iterative(const node_type& local_root) {

    _nodes_to_explore.emplace(std::move(local_root));
    while (!_nodes_to_explore.empty()) {
      const node_type current_node = std::move(_nodes_to_explore.top());
      _nodes_to_explore.pop();
      _process_node(current_node);
    }
  }
  
  void SymmetricExtensionGraph::_dfs(const node_type& current_node) {    
    _process_node(current_node);
  }
}; // namespace topcom

// eof SymmetricExtensionGraph.cc
