////////////////////////////////////////////////////////////////////////////////
// 
// SymmetricFlipGraph.cc
//
//    produced: 24/07/98 jr
// 
////////////////////////////////////////////////////////////////////////////////

#include <sstream>

#include "SymmetricFlipGraph.hh"
#include "PlacingTriang.hh"

namespace topcom {

  // SymmetryWorker:
  SymmetricFlipGraph::SymmetryWorker::SymmetryWorker(SymmetryWorker&& sw) :
    _workerID(sw._workerID),
    _callerptr(sw._callerptr),
    _worker_symmetriesptr(sw._worker_symmetriesptr),
    _tn(sw._tn),
    _equivalent_tnode(sw._equivalent_tnode),
    _gkz(sw._gkz),
    _equivalent_gkz(sw._equivalent_gkz),
    _state(State::idle) {
    MessageStreams::debug() << message::lock
			    << "generated a worker with symmetries " << *_worker_symmetriesptr << std::endl
			    << message::unlock;
  }

  SymmetricFlipGraph::SymmetryWorker::SymmetryWorker(const int                       workerID,
						     SymmetricFlipGraph&             sfg,
						     points_simpidx_symmetries_type& syms) :
    _workerID(workerID),
    _callerptr(&sfg),
    _worker_symmetriesptr(&syms),
    _tn(-1, sfg._confdata.no(), sfg._confdata.rank(), SimplicialComplex()),
    _equivalent_tnode(-1, sfg._confdata.no(), sfg._confdata.rank(), SimplicialComplex()),
    _gkz(sfg._confdata.no()),
    _equivalent_gkz(sfg._confdata.no()),
    _state(State::idle) {
    MessageStreams::debug() << message::lock
			    << "generated a worker with symmetries " << *_worker_symmetriesptr << std::endl
			    << message::unlock;
  }

  void SymmetricFlipGraph::SymmetryWorker::find_old_symmetry_class() {

    // some other thread may have found that current_partial_triang is not new:
    if (_callerptr->_location_of_old_symmetry_class != 0) {
      return;
    }
    if (_worker_symmetriesptr->empty()) {
      return;
    }
    
    for (points_simpidx_symmetries_type::iterator iter = _worker_symmetriesptr->begin();
	 iter != _worker_symmetriesptr->end();
	 ++iter) {
      
      // some other thread may have found that current_partial_triang is not new:
      if (_callerptr->_location_of_old_symmetry_class != 0) {
	return;
      }
      std::pair<symmetryptr_type, Symmetry>& g_pair(*iter);

      // use GKZ vectors for preliminary check:
      if ((CommandlineOptions::use_gkz() || CommandlineOptions::output_gkzvecs()) && _callerptr->_confdata.has_voltable()) {
	g_pair.first->map_into(_gkz, _equivalent_gkz);
	if (_callerptr->_known_gkzs.find(_equivalent_gkz) == _callerptr->_known_gkzs.end()) {
	  continue;
	}
      }
      
      // map tnode depending on whether simplex-index representation is present:
      // if yes, map the triangulation's index set; if no, map all simplices
      // in the ordinary way:
      _callerptr->_map_into(g_pair, _tn, _equivalent_tnode);
      
      // old direct method is deprecated:
      // const TriangNode& equivalent_tnode(g.map(_tn));
    
      tnode_container_type::iterator find_previous_iter = _callerptr->_previous_triangs.find(_equivalent_tnode);
      if (find_previous_iter != _callerptr->_previous_triangs.end()) {
	std::lock_guard<std::mutex> pushresults_guard(_callerptr->_old_symmetry_class_pushresults_mutex);
	_callerptr->_search_orbitsize = 0;
	_callerptr->_output_orbitsize = 0;
	_callerptr->_find_iter = std::move(find_previous_iter);
	_callerptr->_transformation = *g_pair.first; // do not move; reference to stable data
	_callerptr->_representative = _equivalent_tnode; // do not move; member should stay intact
	_callerptr->_location_of_old_symmetry_class = 1;
	return;
      }
      tnode_container_type::iterator find_new_iter = _callerptr->_new_triangs.find(_equivalent_tnode);
      if (find_new_iter != _callerptr->_new_triangs.end()) {
	std::lock_guard<std::mutex> pushresults_guard(_callerptr->_old_symmetry_class_pushresults_mutex);
	_callerptr->_search_orbitsize = 0;
	_callerptr->_output_orbitsize = 0;
	_callerptr->_find_iter = std::move(find_new_iter);
	_callerptr->_transformation = *g_pair.first; // do not move; reference to stable data
	_callerptr->_representative = _equivalent_tnode; // do not move; member should stay intact
	_callerptr->_location_of_old_symmetry_class = 2;
	return;
      }
    }
    return;
  }

  void SymmetricFlipGraph::SymmetryWorker::build_orbit_with_searchpred() const {
    for (points_simpidx_symmetries_type::const_iterator iter = _worker_symmetriesptr->begin();
	 iter != _worker_symmetriesptr->end();
	 ++iter) {
      const std::pair<symmetryptr_type, Symmetry>& g_pair(*iter);
      if (!_callerptr->_node_symmetryptrs.second.empty()
	  && (_callerptr->_node_symmetryptrs.second.find(g_pair.first) != _callerptr->_node_symmetryptrs.second.end())) {
	
	// g is in stabilizer of tnode - nothing new:
	continue;
      }

      // generic map (via index set if present, via set if not):
      const TriangNode equivalent_tnode(std::move(_callerptr->_map(g_pair, _tn)));
      TriangResult equivalent_triangresult(_callerptr->_confdata,
					   equivalent_tnode.ID(),
					   _workerID,
					   0,
					   _callerptr->_worker_output_orbits[_workerID].size(),
					   equivalent_tnode);
      if (CommandlineOptions::output_gkzvecs()) {
	equivalent_triangresult.make_gkzvec();
      }
      equivalent_triangresult.set_flipdist(_callerptr->_flipdist);

      // check whether we already have that image of tnode in one of the workers' orbit:
      bool is_new(true);
      {
	std::lock_guard<std::mutex> lock(_callerptr->_main_mutex);
	for (int i = 0; i < _callerptr->_no_of_threads; ++i) {
	  if (_callerptr->_worker_search_orbits[i].find(equivalent_tnode) != _callerptr->_worker_search_orbits[i].end()) {
	    is_new = false;
	    break;
	  }
	}	
	if (is_new) {
	  
	  // check the search predicate (e.g., regularity):
	  // here we need to check the orbit element directly:
	  if ((_callerptr->_rep_has_search_pred && _callerptr->_predicates_checker.all_search_predicates_invariant())
	      || _callerptr->_predicates_checker.check_search_predicates(_callerptr->_confdata,
									 equivalent_triangresult)) {
	    _callerptr->_worker_search_orbits[_workerID].insert(equivalent_tnode);
	    if ((_callerptr->_rep_has_output_pred && _callerptr->_predicates_checker.all_output_predicates_invariant())
		|| _callerptr->_predicates_checker.check_output_predicates(_callerptr->_confdata,
									   equivalent_triangresult)) {
	      _callerptr->_worker_output_orbits[_workerID].insert(equivalent_tnode);
	      
	      // in this case, we can update some results:
	      if (equivalent_triangresult.make_card() < _callerptr->_min_card) {
		_callerptr->_min_card_triang = equivalent_triangresult.triang();
		_callerptr->_min_card = equivalent_triangresult.card();
	      }
	      if (equivalent_triangresult.make_supportcard() < _callerptr->_min_support_card) {
		_callerptr->_min_support_triang = equivalent_triangresult.triang();
		_callerptr->_min_support_card = equivalent_triangresult.supportcard();
	      }
	      if (!_callerptr->_options.is_downstream_call()) {
		_callerptr->_triang_outputter.print_triang(MessageStreams::result(), equivalent_triangresult);
	      }
	    }
	    else {
	      equivalent_tnode.invalidate();
	      if (_callerptr->_options.stop_at_regular() && equivalent_triangresult.is_regular()) {
		_callerptr->_final_triang = equivalent_triangresult.triang();
		_callerptr->_regular_triang_found = true;
		return;
	      }
	      if (_callerptr->_options.stop_at_target() && equivalent_triangresult.is_targetequiv()) {
		_callerptr->_final_triang = equivalent_triangresult.triang();
		_callerptr->_target_triang_found = true;
		return;
	      }
	    }
	  }
	}
      }
    }
  }

  // interface with caller:
  void SymmetricFlipGraph::SymmetryWorker::pass_work_for_find_old_symmetry_class(const TriangNode& tn,
										 const Vector&     gkz) {
    _tn = tn;
    _gkz = gkz;
    std::lock_guard<std::mutex> lock(_callerptr->_main_mutex);
    _state = State::hired_for_find_old_symmetry_class;
  }
  
  void SymmetricFlipGraph::SymmetryWorker::pass_work_for_build_orbit_with_searchpred(const TriangNode& tn,
										     const Vector&     gkz) {
    _tn = tn;
    _gkz = gkz;
    std::lock_guard<std::mutex> lock(_callerptr->_main_mutex);
    _state = State::hired_for_build_orbit_with_searchpred;
  }
    
  void SymmetricFlipGraph::SymmetryWorker::stop_worker() {
    _state = State::stopped;
    worker_condition.notify_one();
  }

  bool SymmetricFlipGraph::SymmetryWorker::_wake_up() const {
    return (_state != State::idle);
  }
  
  void SymmetricFlipGraph::SymmetryWorker::operator()() {

    // perform the postponed generation of simpidx symmetries:
    if (CommandlineOptions::simpidx_symmetries()) {
      size_type cnt = 0;
      MessageStreams::verbose() << message::lock
				<< "inserting simpidx symmetries for worker " << _workerID << " ..." << std::endl
				<< message::unlock;
      for (points_simpidx_symmetries_type::iterator iter = _worker_symmetriesptr->begin();
	   iter != _worker_symmetriesptr->end();
	   ++iter) {
	iter->second = std::move(iter->first->simpidx_symmetry(_callerptr->_confdata.rank()));
	if (++cnt % CommandlineOptions::report_frequency() == 0) {
	  MessageStreams::verbose() << message::lock
				    << cnt << " symmetries processed so far." << std::endl
				    << message::unlock;
	}
      }
      MessageStreams::verbose() << message::lock
				<< "... done" << std::endl
				<< message::unlock;
    }
    
    while (_state != State::stopped) {
      MessageStreams::debug() << message::lock
			      << "worker " << _workerID
			      << " processing state " << _state
			      << " with " << _callerptr->_no_of_busy_threads
			      << " done threads ..." << std::endl
			      << message::unlock;

      // wait until threre is work:
      while (_state == State::idle) {
	MessageStreams::debug() << message::lock
				<< "worker " << _workerID << " waiting ..." << std::endl
				<< message::unlock;

	// wait for new work from main thread:
	std::unique_lock<std::mutex> worker_lock(_callerptr->_main_mutex);
	++_callerptr->_no_of_waiting_threads;
	worker_condition.wait(worker_lock, [this] { return _wake_up(); });
	--_callerptr->_no_of_waiting_threads;
	MessageStreams::debug() << message::lock
				<< "worker " << _workerID << " waking up ..." << std::endl
				<< message::unlock;
      }

      // check the type of work:
      if (_state == State::hired_for_find_old_symmetry_class) {
	MessageStreams::debug() << message::lock
				<< "worker " << _workerID << " checking for old symmetry class ..." << std::endl
				<< message::unlock;

	// do the corresponding work:
	find_old_symmetry_class();
	_state = State::done;
      }
      else if (_state == State::hired_for_build_orbit_with_searchpred) {
	MessageStreams::debug() << message::lock
				<< "worker " << _workerID << " building orbit ..." << std::endl
				<< message::unlock;

	// do the corresponding work:
	build_orbit_with_searchpred();
	_state = State::done;
      }

      // report that work is done:
      if (_state == State::done) {
	MessageStreams::debug() << message::lock
				<< "worker " << _workerID << " done ..." << std::endl
				<< message::unlock;
	std::lock_guard<std::mutex> main_lock(_callerptr->_main_mutex);
	--_callerptr->_no_of_busy_threads;
	MessageStreams::debug() << message::lock
				<< "notifying main thread ..." << std::endl
				<< message::unlock;
	if (_callerptr->_threads_done()) {
	  _callerptr->_main_condition.notify_one();
	}
	_state = State::idle;
      }
    }
  }

  // end of SymmetryWorker methods.


  // statics:
  const thread_local Cache<SimplicialComplex> SymmetricFlipGraph::_dummy_global_cache;
  thread_local       Cache<SimplicialComplex> SymmetricFlipGraph::_dummy_local_cache;

  
  // auxiliary functions only for this module:
  TriangNode SymmetricFlipGraph::_map(const std::pair<symmetryptr_type, Symmetry>& sympair,
				      const TriangNode& tnode) const {
    if (sympair.second.n() == 0) {

      // we have not built the simplex-index representation of the symmetry group
      // (usually because of memory consumption), thus, we have to use the
      // TOPCOM's slower legacy method:
      return sympair.first->map(tnode);
    }
    else {
      return _map_via_indexset(sympair.second, tnode);
    }
  }

  void SymmetricFlipGraph::_map_into(const std::pair<symmetryptr_type, Symmetry>& sympair,
				     const TriangNode& tnode,
				     TriangNode& result) const {
    if (sympair.second.n() == 0) {
      result.clear();
      sympair.first->map_into(tnode, result);
    }
    else {
      _map_into_via_indexset(sympair.second, tnode, result);
    }
  }

  TriangNode SymmetricFlipGraph::_map_via_indexset(const Symmetry& g_simpidx,
						   const TriangNode& tnode) const {
    TriangNode result(tnode.ID(), tnode.no(), tnode.rank(), SimplicialComplex());

    result.replace_indexset(tnode.rank(), std::move(g_simpidx.map(tnode.index_set_pure())));
    return result;
  }

  void SymmetricFlipGraph::_map_into_via_indexset(const Symmetry& g_simpidx,
						  const TriangNode& tnode,
						  TriangNode& result) const {
    const SimplicialComplex::IndexSet& tnode_indexset = tnode.index_set_pure();
    SimplicialComplex::IndexSet result_indexset = g_simpidx.map(tnode_indexset);
    result.replace_indexset(tnode.rank(), std::move(result_indexset));
  }

  symmetryptr_datapair SymmetricFlipGraph::_stabilizer_ptrs(const TriangNode& tnode,
							    const Vector&     gkz) const {
    if (_active_points_simpidx_symmetries.empty()) {
      return symmetryptr_datapair();
    }
    else {
      if (_active_points_simpidx_symmetries.begin()->second.n() > 0) {
	MessageStreams::debug() << message::lock
				<< "computing _stabilizerptrs_via_indexset ..." << std::endl
				<< message::unlock;
	return _stabilizer_ptrs_via_indexset(tnode, gkz);
      }
      else {
	MessageStreams::debug() << message::lock
				<< "computing _stabilizer_ptrs ..." << std::endl
				<< message::unlock;
	if (_confdata.has_voltable() && CommandlineOptions::use_gkz()) {

	  // in this case, we have a fingerprint:
	  return _active_symmetries.stabilizer_ptrs(tnode, gkz);
	}
	else {
	  return _active_symmetries.stabilizer_ptrs(tnode);
	}
      }
    }
  }

  symmetryptr_datapair SymmetricFlipGraph::_stabilizer_ptrs_via_indexset(const TriangNode& tnode,
									 const Vector&     gkz) const {
    symmetryptr_datapair result;
    for (points_simpidx_symmetries_type::const_iterator iter = _active_points_simpidx_symmetries.begin();
	 iter != _active_points_simpidx_symmetries.end();
	 ++iter) {
      if (_confdata.has_voltable() && CommandlineOptions::use_gkz()) {
	if (!iter->first->fixes(gkz)) {
	  continue;
	}
      }
      if (iter->second.fixes(tnode.index_set_pure())) {
	result.first.push_back(iter->first);
	result.second.insert(iter->first);
      }
    }
    return result;
  }

  bool SymmetricFlipGraph::_threads_done() const {
    return (_no_of_busy_threads == 0);
  }
  
  int SymmetricFlipGraph::_old_symmetry_class(const TriangNode& tnode,
					      const Vector&     gkz) {

    static const bool local_debug = false;

    MessageStreams::debug() << message::lock
			    << "checking old symmetry class w.r.t. "
			    << _active_points_simpidx_symmetries.size()
			    << " symmetries ..."
			    << std::endl
			    << message::unlock;
    
    // return values are:
    // 0: tnode represents a new symmetry class
    // -1: tnode is contained in previous triangulations
    // -2: tnode is contained in new triangulations
    // +1: an element of the orbit of tnode is in previous triangulations
    // +2: an element of the orbit of tnode is in new triangulations
    // updated data:
    // _representative: the element in the orbit of tnode that is in previous/new triangulations
    // _find_iter: an iterator in previous/new triangulations pointing to _representative
    // _transformation: a symmetry mapping tnode to _representative
    // _search_orbitsize: the size of the orbit of tnode with search predicate
    // _output_orbitsize: the size of the orbit of tnode with search and output predicate
    // _rep_has_search_pred: whether or not tnode satisfies the search predicate
    // _rep_has_output_pred: whether or not tnode satisfies the search and the output predicate
  
    // the case of non-simple BFS:
  
    // handle the identity individually:
    if ((_find_iter = _previous_triangs.find(tnode)) != _previous_triangs.end()) {
      _location_of_old_symmetry_class = 0;
      _search_orbitsize = 0;
      _output_orbitsize = 0;
      return -1;
    }
    if ((_find_iter = _new_triangs.find(tnode)) != _new_triangs.end()) {
      _location_of_old_symmetry_class = 0;
      _search_orbitsize = 0;
      _output_orbitsize = 0;
      return -2;
    }

    // if (false) {
    if (CommandlineOptions::parallel_symmetries()) {
      
      // multi-threaded version of non-simple BFS:
      MessageStreams::debug() << message::lock
			      << "starting parallel symmetry check with "
			      << _no_of_waiting_threads << " waiting threads and "
			      << _no_of_busy_threads << " done threads ..." << std::endl
			      << message::unlock;
      {
	std::lock_guard<std::mutex> main_lock(_main_mutex);
	_location_of_old_symmetry_class = 0;
	_no_of_busy_threads = 0;
      }
      for (int i = 0; i < _no_of_threads; ++i) {
	MessageStreams::debug() << message::lock
				<< "main hiring and notifying worker " << i << " for symmetry check ..." << std::endl
				<< message::unlock;
	_symmetry_workers[i].pass_work_for_find_old_symmetry_class(tnode, gkz);
	_symmetry_workers[i].worker_condition.notify_one();
	std::lock_guard<std::mutex> main_lock(_main_mutex);
	++_no_of_busy_threads;
      }
      
      while (!_threads_done()) {
	MessageStreams::debug() << message::lock
				<< "main waiting for symmetry checks ..." << std::endl
				<< message::unlock;

	// wait for threads to finish work:
	std::unique_lock<std::mutex> main_lock(_main_mutex);
	_main_condition.wait(main_lock, [this] { return _threads_done(); });
	MessageStreams::debug() << message::lock
				<< "... main waking up" << std::endl
				<< message::unlock;
      }

      MessageStreams::debug() << message::lock
			      << "... completed parallel symmetry check" << std::endl
			      << message::unlock;
      
      MessageStreams::debug() << message::lock
			      << "... done" << std::endl
			      << message::unlock;
      if (_location_of_old_symmetry_class != 0) {
	_search_orbit.clear();
	_search_orbitsize = 0;
	_output_orbit.clear();
	_output_orbitsize = 0;
	return _location_of_old_symmetry_class;
      }
    }
    else {
      // single-threaded version of non-simple BFS:
    
      // first check whether we already found an equivalent triangluation:
      for (points_simpidx_symmetries_type::const_iterator iter = _active_points_simpidx_symmetries.begin();
	   iter != _active_points_simpidx_symmetries.end();
	   ++iter) {
	const std::pair<symmetryptr_type, Symmetry>& g_pair(*iter);

	// bool gkz_result;
	if (CommandlineOptions::use_gkz() && _confdata.has_voltable()) {
	
	  // first check is by the GKZ vector:
	  g_pair.first->map_into(gkz, _equivalent_gkz);
	  if (_known_gkzs.find(_equivalent_gkz) == _known_gkzs.end()) {
	    
	    // GKZ is new, thus, tnode is new:	  
	    continue;
	    // gkz_result = false;
	  }
	  else {
	    // gkz_result = true;
	  }
	}

	// if not, further investigations are necessary in case also non-regular
	// triangulations are traversed:
	
	// map tnode by mapping its index set by means of the alternative representation
	// of the symmetry; this way, not all simplices in tnode have to be mapped
	// individually:
	_map_into(g_pair, tnode, _representative);

	// the old direct method is deprecated:
	// _representative = g_pair.first.map(tnode);
	if ((_find_iter = _previous_triangs.find(_representative)) != _previous_triangs.end()) {
	  _transformation = *g_pair.first;
	  _search_orbit.clear();
	  _search_orbitsize = 0;
	  _output_orbit.clear();
	  _output_orbitsize = 0;
	  return 1;
	}
	if ((_find_iter = _new_triangs.find(_representative)) != _new_triangs.end()) {
	  _transformation = *g_pair.first;
	  _search_orbit.clear();
	  _search_orbitsize = 0;
	  _output_orbit.clear();
	  _output_orbitsize = 0;
	  return 2;
	}
      }
    }

    // from here, we know that we found a new symmetry class;
    // we next need to check the predicates for it and its orbit:
    bool have_node_symmetryptrs = false;
    
    // check the search predicate, e.g., regularity, for the 
    // discovered representative tnode of the new symmetry class
    // and save all relevant information in triangresult:
    TriangResult triangresult(_confdata,
			      tnode.ID(),
			      _ID,
			      0,
			      0,
			      tnode);
    if (CommandlineOptions::output_gkzvecs()) {
      if (gkz.is_zero()) {
	triangresult.make_gkzvec();
      }
      else {
	triangresult.set_gkzvec(gkz);
      }
    }
    triangresult.set_flipdist(_flipdist);
    bool search_orbitsize_known = false;
    bool output_orbitsize_known = false;
    if (_predicates_checker.check_search_predicates(_confdata, triangresult)) {
    
      // tnode has all search predicates:
      _rep_has_search_pred = true;

      // _search_orbit collects all triangulations in orbit having all search predicates:
      _search_orbit.insert(tnode);
      
      // compute stabilizer, if possible using the index set representation:
      _node_symmetryptrs = _stabilizer_ptrs(tnode, gkz);
      have_node_symmetryptrs = true;
      if (_predicates_checker.check_output_predicates(_confdata, triangresult)) {

	// tnode has all output predicates:
	_rep_has_output_pred = true;

	// _output_orbit collects all triangulations in orbit having all search and output predicates:
	_output_orbit.insert(tnode);
	
	// in this case, we can update some results:
	if (triangresult.make_card() < _min_card) {
	  _min_card_triang = triangresult.triang();
	  _min_card = triangresult.card();
	}
	if (triangresult.make_supportcard() < _min_support_card) {
	  _min_support_triang = triangresult.triang();
	  _min_support_card = triangresult.supportcard();
	}

	// print triangulation if requested:
	if (!_options.is_downstream_call()) {
	  _triang_outputter.print_triang(MessageStreams::result(), triangresult);
	}
      }
      else {
	_rep_has_output_pred = false;
	tnode.invalidate();
	if (_options.stop_at_regular() && triangresult.is_regular()) {
	  _final_triang = triangresult.triang();
	  _regular_triang_found = true;
	}
	if (_options.stop_at_target() && triangresult.is_targetequiv()) {
	  _final_triang = triangresult.triang();
	  _target_triang_found = true;
	}
      }
    }
    else {
      _rep_has_search_pred = false;
    }
    if (_options.no_symmetries()) {
      if (_rep_has_search_pred) {
	_search_orbitsize = 1;
      }
      else {
	_search_orbitsize = 0;
      }
      search_orbitsize_known = true;
      if (_rep_has_output_pred) {
	_output_orbitsize = 1;
      }
      else {
	_output_orbitsize = 0;
      }
      output_orbitsize_known = true;
    }
    else {
      if (_predicates_checker.all_search_predicates_invariant()) {
	if (_rep_has_search_pred) {
	  
	  // stabilizer formula:
	  _search_orbitsize = (_active_symmetries.size() + 1) / (_node_symmetryptrs.first.size() + 1);
	}
	else {
	  _search_orbitsize = 0;
	}
	search_orbitsize_known = true;
	if (_predicates_checker.all_output_predicates_invariant()) {
	  if (_rep_has_output_pred) {
	    
	    // stabilizer formula:
	    _output_orbitsize = (_active_symmetries.size() + 1) / (_node_symmetryptrs.first.size() + 1);
	  }
	  else {
	    _output_orbitsize = 0;
	  }
	  output_orbitsize_known = true;  
	}
      }
    }
    if (_options.no_symmetries()	
	||
	(
	 _predicates_checker.all_search_predicates_invariant()
	 && _predicates_checker.all_output_predicates_invariant()
	 )
	) {
      return 0;
    }

    // compute stabilizer, if possible via index sets, if not yet done:
    MessageStreams::debug() << message::lock
			    << "some predicates are not invariant - orbit building necessary." << std::endl
			    << message::unlock;
    if (!have_node_symmetryptrs) {
      _node_symmetryptrs = _stabilizer_ptrs(tnode, gkz);
    }
    if (CommandlineOptions::parallel_symmetries()) {

      // multi-threaded version of non-simple orbit-building:
      MessageStreams::debug() << message::lock
			      << "starting parallel orbit building with "
			      << _no_of_waiting_threads << " waiting threads and "
			      << _no_of_busy_threads << " done threads ..." << std::endl
			      << message::unlock;
      {
	std::lock_guard<std::mutex> main_lock(_main_mutex);
	_no_of_busy_threads = 0;
	for (int i = 0; i < _no_of_threads; ++i) {
	  _worker_search_orbits[i].clear();
	  _worker_output_orbits[i].clear();
	}
      }
      for (int i = 0; i < _no_of_threads; ++i) {
	MessageStreams::debug() << message::lock
				<< "main hiring and notifying worker " << i << " for orbit building ..." << std::endl
				<< message::unlock;
	_symmetry_workers[i].pass_work_for_build_orbit_with_searchpred(tnode, gkz);
	_symmetry_workers[i].worker_condition.notify_one();
	std::lock_guard<std::mutex> main_lock(_main_mutex);
	++_no_of_busy_threads;
      }
      while (!_threads_done()) {
	MessageStreams::debug() << message::lock
				<< "main waiting for orbit building ..." << std::endl
				<< message::unlock;
	
	// wait for threads to finish work:
	std::unique_lock<std::mutex> main_lock(_main_mutex);
	_main_condition.wait(main_lock, [this] { return _threads_done(); });
	MessageStreams::debug() << message::lock
				<< "... main waking up" << std::endl
				<< message::unlock;
      }      
      MessageStreams::debug() << message::lock
			      << "... completed parallel orbit building" << std::endl
			      << "... done" << std::endl
			      << message::unlock;

      // collect the results:
      for (int i = 0; i < _no_of_threads; ++i) {
	MessageStreams::debug() << message::lock
				<< "... main collecting results from worker " << i << " ..." << std::endl
				<< message::unlock;
	for (orbit_type::iterator iter = _worker_search_orbits[i].begin();
	     iter != _worker_search_orbits[i].end();
	     ++iter) {
	  _search_orbit.insert(std::move(*iter));
	}
	for (orbit_type::iterator iter = _worker_output_orbits[i].begin();
	     iter != _worker_output_orbits[i].end();
	     ++iter) {
	  _output_orbit.insert(std::move(*iter));
	}
      }
      _search_orbitsize = _search_orbit.size();
      _search_orbit.clear();
      _output_orbitsize = _output_orbit.size();
      _output_orbit.clear();
      MessageStreams::debug() << message::lock
			      << "workers final result on _search_orbitsize = " << _search_orbitsize << '\n'
			      << "workers final result on _output_orbitsize = " << _output_orbitsize << std::endl
			      << message::unlock;
    }
    else {
      // single-threaded version of non-simple orbit-building:
    
      // next, we need to check the orbit of tnode for the search predicate (e.g., regularity):
      triang_container_type complete_orbit;
      for (points_simpidx_symmetries_type::const_iterator iter = _active_points_simpidx_symmetries.begin();
	   iter != _active_points_simpidx_symmetries.end();
	   ++iter) {
	if (_node_symmetryptrs.second.find(iter->first) != _node_symmetryptrs.second.end()) {

	  // g is in stabilizer of tnode - nothing new:
	  continue;
	}

	// map via index set:
	_representative = std::move(_map(*iter, tnode));
	if (complete_orbit.find(_representative) == complete_orbit.end()) {

	  // a new element in the orbit:
	  complete_orbit.insert(_representative);
	  TriangResult equivalent_triangresult(_confdata,
					       _representative.ID(),
					       _ID,
					       0,
					       _output_orbit.size(),
					       _representative);
	  if (CommandlineOptions::output_gkzvecs()) {
	    equivalent_triangresult.make_gkzvec();
	  }
	  equivalent_triangresult.set_flipdist(_flipdist);

	  // check whether we already have that image of tnode in the search orbit:
	  if (_search_orbit.find(_representative) == _search_orbit.end()) {
	    
	    // if not, check the search predicate (e.g., regularity):
	    if ((_rep_has_search_pred && _predicates_checker.all_search_predicates_invariant())
		|| _predicates_checker.check_search_predicates(_confdata, equivalent_triangresult)) {
	      _search_orbit.insert(_representative);
	      
	      // next, check whether we already have that image of tnode in the output orbit:
	      if (_output_orbit.find(_representative) == _output_orbit.end()) {

		// if not, check the output predicate (e.g., non-regularity):
		if ((_rep_has_output_pred && _predicates_checker.all_output_predicates_invariant())
		    || _predicates_checker.check_output_predicates(_confdata, equivalent_triangresult)) {
		  
		  _output_orbit.insert(_representative);
		  
		  // in this case, we can update some results:
		  if (equivalent_triangresult.make_card() < _min_card) {
		    _min_card_triang = equivalent_triangresult.triang();
		    _min_card = equivalent_triangresult.card();
		  }
		  if (equivalent_triangresult.make_supportcard() < _min_support_card) {
		    _min_support_triang = equivalent_triangresult.triang();
		    _min_support_card = equivalent_triangresult.supportcard();
		  }	      
		  if (!_options.is_downstream_call()) {
		    _triang_outputter.print_triang(MessageStreams::result(), equivalent_triangresult);
		  }
		}
		else {
		  _representative.invalidate();
		  if (_options.stop_at_regular() && equivalent_triangresult.is_regular()) {
		    _final_triang = equivalent_triangresult.triang();
		    _regular_triang_found = true;
		  }
		  if (_options.stop_at_target() && equivalent_triangresult.is_targetequiv()) {
		    _final_triang = equivalent_triangresult.triang();
		    _target_triang_found = true;
		  }
		}
	      }
	    }
	  }
	}
      }
      _search_orbitsize = _search_orbit.size();
      _search_orbit.clear();
      _output_orbitsize = _output_orbit.size();
      _output_orbit.clear();
    }
    return 0;
  }

  void SymmetricFlipGraph::_mark_equivalent_flips(const TriangNode&                     tnode, 
						  const tnode_container_type::iterator  find_iter,
						  const FlipRep&                        fliprep) {
#ifdef CHECK_MARK
    MessageStreams::debug() << message::lock
			    << "flip before marking:" << std::endl
			    << find_iter->key() << "->" << find_iter->data() << std::endl
			    << message::unlock;
#endif
    
    // mark this flip:
#ifdef TOPCOM_CONTAINERS
    find_iter->dataptr()->mark_flip(fliprep);
#else
    find_iter->second.mark_flip(fliprep);
#endif
    
    // mark all equivalent flips:
    for (symmetryptr_iterdata::const_iterator iter = _node_symmetryptrs.first.begin();
	 iter != _node_symmetryptrs.first.end();
	 ++iter) {
      const Symmetry& g = **iter;
#ifdef TOPCOM_CONTAINERS
      find_iter->dataptr()->mark_flip(g.map(fliprep));
#else
      find_iter->second.mark_flip(g.map(fliprep));
#endif
    }
#ifdef CHECK_MARK
    MessageStreams::debug() << message::lock
			    << "flip after marking:" << std::endl
			    << find_iter->key() << "->" << find_iter->data() << std::endl
			    << message::unlock;
#endif
  }

  void SymmetricFlipGraph::_mark_equivalent_flips(const TriangNode& tnode, 
						  TriangFlips&      tflips,
						  const FlipRep&    fliprep) {
#ifdef CHECK_MARK
    MessageStreams::debug() << message::lock
			    << "flips before marking:" << std::endl
			    << tflips << std::endl
			    << message::unlock;
#endif
    
    // mark this flip:
    tflips.mark_flip(fliprep);
    
    // mark all equivalent flips:
    for (symmetryptr_iterdata::const_iterator iter = _node_symmetryptrs.first.begin();
	 iter != _node_symmetryptrs.first.end();
	 ++iter) {
      const Symmetry& g(**iter);
      tflips.mark_flip(g.map(fliprep));
    }
#ifdef CHECK_MARK
    MessageStreams::debug() << message::lock
			    << "flips after marking:" << std::endl
			    << tflips << std::endl
			    << message::unlock;
#endif
  }

  void SymmetricFlipGraph::_process_newtriang(const TriangNode&     current_triang,
					      const TriangFlips&    current_flips,
					      const TriangNode&     next_triang,
					      const FlipRep&        current_fliprep) {
    if (CommandlineOptions::use_gkz() && _confdata.has_voltable()) {
      _new_gkz = std::move(_gkz(next_triang));
    }
    const int where(_old_symmetry_class(next_triang, _new_gkz));
    if (where < 0) {
    
      // next_triang was found before: mark all equivalent flips in next_triang:
      const FlipRep& inversefliprep(current_fliprep.inverse());

      stabilizer_container_type::const_iterator stabiter = _stabilizers.find(next_triang);
      if (stabiter != _stabilizers.end()) {
	_node_symmetryptrs = stabiter->second;
      }
      else {
	_node_symmetryptrs = _stabilizer_ptrs(next_triang, _new_gkz);
      }
      
      _mark_equivalent_flips(next_triang, _find_iter, inversefliprep);

      // only output the flip between untransformed triangulations
      // if both incident triangulations satisfy the output predicate:
#ifdef TOPCOM_CONTAINERS
      const TriangNode& previous_triang(_find_iter->key());
#else
      const TriangNode& previous_triang(_find_iter->first);
#endif
      if (current_triang.valid() && previous_triang.valid()) {
	
	// output flip to found triangulation pointed to by _find_iter:
	FlipResult flipresult(_confdata,
			      _flipcount,
			      current_triang.ID(),
			      previous_triang.ID(),
			      current_fliprep);
	++_flipcount;
	_flip_outputter.print_flip(MessageStreams::result(), flipresult);
      }
      return;
    }
    if (where > 0) {
    
      // a triangulation equivalent to next_triang was found before: mark all equivalent flips in representative:
      const FlipRep& inversefliprep(_transformation.map(current_fliprep.inverse()));

      stabilizer_container_type::const_iterator stabiter = _stabilizers.find(_representative);
      if (stabiter != _stabilizers.end()) {      
	_node_symmetryptrs = stabiter->second;
      }
      else {
	_node_symmetryptrs = _stabilizer_ptrs(_representative, _equivalent_gkz);
      }
      _mark_equivalent_flips(_representative, _find_iter, inversefliprep);
      return;
    }
    if ((where == 0) && (_search_orbitsize > 0)) {
    
      // we found at least one new triangulation in orbit satisfying 
      // the search predicate; thus we must save the representative
      TriangFlips next_flips = TriangFlips(_confdata,
					   _options,
					   current_triang, 
					   current_flips, 
					   next_triang, 
					   Flip(current_triang, current_fliprep),
					   _node_symmetryptrs);
      _mark_equivalent_flips(next_triang, next_flips, current_fliprep.inverse());

      // only count the triangulation if at least one in its orbit satisfies the output predicate:
      if (_output_orbitsize > 0) {
	++_symcount;
	_totalcount += _output_orbitsize;
	
	// only output the flip if both incident triangulations satisfy the output predicate:
	if (current_triang.valid() && next_triang.valid()) {
	  FlipResult flipresult(_confdata,
				_flipcount,
				current_triang.ID(),
				next_triang.ID(),
				current_fliprep);
	  ++_flipcount;
	  _flip_outputter.print_flip(MessageStreams::result(), flipresult);
	}
      }
#ifdef CHECK_NEW
      MessageStreams::forced() << message::lock
			       << "Checking whether " << next_triang << " is new:" << std::endl
			       << "\t _all_triangs: " << std::endl
			       << "\t " << _all_triangs << std::endl
			       << "\t current new triangs:" << std::endl
			       << "\t " << _new_triangs << std::endl
			       << "\t current previous triangs:" << std::endl
			       << "\t " << _previous_triangs << std::endl
			       << message::unlock;
      if (_all_triangs.find(next_triang) != _all_triangs.end()) {
	MessageStreams::forced() << message::lock
				 << "\t Error in SymmetricFlipGraph: old triangulation " << std::endl
				 << "\t " << next_triang << std::endl
				 << "\t is not new - exiting" << std::endl
				 << message::unlock;
	exit(1);
      }
      MessageStreams::debug() << message::lock
			      << next_triang << " is new." << std::endl
			      << message::unlock;
#endif
      
      // finally register next_triang->next_flips in hash map _new_triangs:
      _new_triangs.insert(std::pair<TriangNode, TriangFlips>(next_triang, next_flips));
      ++_storedcount;
      _stabilizers.insert(std::pair<TriangNode, symmetryptr_datapair>(next_triang, _node_symmetryptrs));
      if (CommandlineOptions::use_gkz() && _confdata.has_voltable()) {
	_known_gkzs.insert(_new_gkz);
      }
    }
  }

  void SymmetricFlipGraph::_process_flips(const TriangNode&     current_triang,
					  const TriangFlips&    current_flips) {
#ifdef SUPER_VERBOSE
    MessageStreams::debug() << message::lock
			    << "current_triang" << std::endl
			    << current_triang << std::endl
			    << message::unlock;
    MessageStreams::debug() << message::lock
			    << "current_flips" << std::endl
			    << current_flips << std::endl
			    << message::unlock;
#endif
    for (MarkedFlips::const_iterator iter = current_flips.flips().begin();
	 iter !=current_flips.flips().end(); 
	 ++iter) {
      if ((*iter).second) {

	// flip has been marked (an equivalent flip has been followed already):
	continue;
      }
      if (_options.stop_at_regular() && _regular_triang_found) {
	break;
      }
      if (_options.stop_at_target() && _target_triang_found) {
	break;
      }

#ifdef SUPER_VERBOSE
#ifdef TOPCOM_FLIPS
      MessageStreams::debug() << message::lock
			      << "flipping flip " << iter->key() 
			      << " = " << Flip(current_triang, iter->key()) << std::endl
			      << message::unlock;      
#else
      MessageStreams::debug() << message::lock
			      << "flipping flip " << iter->first 
			      << " = " << Flip(current_triang, iter->first) << std::endl
			      << message::unlock;
#endif      
#endif
#ifdef TOPCOM_FLIPS
      const FlipRep current_fliprep(iter->key());
#else
      const FlipRep current_fliprep(iter->first);
#endif
      ++_nodecount;
      report_flip_node_progress(MessageStreams::verbose());
      const Flip flip(current_triang, current_fliprep);
      const TriangNode next_triang(_symcount, current_triang, flip);
      if (_options.stop_at_regular()) {
	if (_global_triangcache.is_cached(next_triang) || _local_triangcache.is_cached(next_triang)) {
	  MessageStreams::debug() << message::lock
				  << "... worker " << _ID << ": cache hit in bfs." << std::endl
				  << message::unlock;
	  next_triang.invalidate();
	  _final_triang = next_triang;
	  _regular_triang_found = true;
	}
      }
      if (_options.stop_at_target()) {
	if (_global_triangcache.is_cached(next_triang) || _local_triangcache.is_cached(next_triang)) {
	  MessageStreams::debug() << message::lock
				  << "... worker " << _ID << ": cache hit in bfs." << std::endl
				  << message::unlock;
	  next_triang.invalidate();
	  _final_triang = next_triang;
	  _target_triang_found = true;
	}
      }
      _new_triangcache.add(next_triang);
      _process_newtriang(current_triang, current_flips, next_triang, current_fliprep);
    }
  }

  void SymmetricFlipGraph::_bfs_step() {
    
    // go through all triangulations found so far:
    while (!_previous_triangs.empty()) {
      if (_options.stop_at_regular() && _regular_triang_found) {
	break;
      }
      tnode_container_type::const_iterator iter(_previous_triangs.begin());
#ifdef TOPCOM_CONTAINERS
      const TriangNode current_triang(iter->key());
#else
      const TriangNode current_triang(iter->first);
#endif
#ifdef TOPCOM_CONTAINERS
      const TriangFlips current_flips(iter->data());
#else
      const TriangFlips current_flips(iter->second);
#endif
      _process_flips(current_triang, current_flips);
      _previous_triangs.erase(current_triang);
      --_storedcount;
      _stabilizers.erase(current_triang);
      if ((CommandlineOptions::use_gkz() || CommandlineOptions::output_gkzvecs())
	  && _confdata.has_voltable()
	  && CommandlineOptions::check_regular()) {

	// we can only erase old GKZ vectors in the regular case;
	// for non-regular triangulations, we need to keep all the GKZ vectors:
	_known_gkzs.erase(_gkz(current_triang));
      }

      if (Signal::signal_received()) {

	// here, dump status only at signal received
	// if requested and not called by a client dumping itself:
	if (!_options.is_downstream_call()) {
	  trigger_checkpoint(true);
	  _ready_for_new_checkpoint = false;
	}

	// execute signal:
	break;
      }
    }
#ifdef CHECK_NEW
    for (tnode_container_type::const_iterator iter = _new_triangs.begin();
	 iter != _new_triangs.end();
	 ++iter) {
#ifdef TOPCOM_CONTAINERS
      _all_triangs[iter->key()] = iter->data();
#else
      _all_triangs[iter->first] = iter->second;
#endif
    }
 #endif
  }

  void SymmetricFlipGraph::_bfs() {  
    while (!_previous_triangs.empty()) {
      if (_options.stop_at_regular() && _regular_triang_found) {
	break;
      }
      if (_new_triangs.empty()) {

	// a new flip-distance class is opened:
	++_flipdist;
      }
      report_flip_distclass_progress(MessageStreams::verbose(), true);
      _bfs_step();
      if (_previous_triangs.empty()) {

	// we came from _bfs_step without an intermediate checkpoint:
	_ready_for_new_checkpoint = true;
      }
#ifdef TOPCOM_CONTAINERS
      tnode_container_type tmp;
      _previous_triangs = _new_triangs;
      _new_triangs = tmp;
#else
      _previous_triangs.swap(_new_triangs);
#endif
 
      // here, dump status if requested also without signal
      // whenever not done before and not called by a client dumping itself:
      if (_ready_for_new_checkpoint && !_options.is_downstream_call()) {
	trigger_checkpoint(true);
	_ready_for_new_checkpoint = false;
      }

      if (Signal::signal_received()) {
	
	// execute signal:
	break;
      }
    }
    report_flip_distclass_progress(MessageStreams::verbose(), true);
  }

  bool SymmetricFlipGraph::_is_lexgkz_smaller(const Vector& gkz,
					      const Vector& new_gkz,
					      const SimplicialComplex& triang,
					      const SimplicialComplex& new_triang,
					      const bool reverse_order,
					      const bool randomdir) {
    if (randomdir) {
      return (
	      (!reverse_order
	       &&
	       (gkz.lexsmaller(new_gkz, _randperm)
		||
		(
		 (gkz == new_gkz)
		 &&
		 triang.lexsmaller(new_triang, triang.rank())))
	       )
	      ||
	      (reverse_order
	       &&
	       (gkz.lexgreater(new_gkz, _randperm)
		||
		(
		 (gkz == new_gkz)
		 &&
		 triang.lexgreater(new_triang, triang.rank())))
	       )
	      );
    }
    else {
      return (
	      (!reverse_order
	       &&
	       (gkz.lexsmaller(new_gkz)
		||
		(
		 (gkz == new_gkz)
		 &&
		 triang.lexsmaller(new_triang, triang.rank())))
	       )
	      ||
	      (reverse_order
	       &&
	       (gkz.lexgreater(new_gkz)
		||
		(
		 (gkz == new_gkz)
		 &&
		 triang.lexgreater(new_triang, triang.rank())))
	       )
	      );
    }
  }

  // the following is only called whenever "_options.lex_increasing_gkz_only()"
  // and "_options.stop_at_regular()" are both true;
  // this results in following only all lex-GKZ increasing flips:
  void SymmetricFlipGraph::_dfs_lexgkz(bool reverse_order,
				       bool randomdir) {

    
    // we set up an alternative regularity check for leaves,
    // since the lex-GKZ-max regular triangulation is unique;
    // we use _final_triang as this unique optimal triang
    // and _target_triang_found as a flag:
    _target_triang_found = false;
    if (_previous_triangs.empty()) {
      MessageStreams::debug() << message::lock
			      << "no seed triangulation present in _previous_triangs - nothing to do."
			      << std::endl
			      << message::unlock;
    }
    else {
      tnode_container_type::const_iterator iter = _previous_triangs.begin();
      _cached_triangs.clear();
      TriangNode tn(iter->first);
      _new_triangcache.add(tn); // this cache holds *all* triangulations found by *any* flip
      ++_nodecount;

      // if an earlier investigation has found a regular triangulation,
      // stop immediately:
      if (_options.stop_at_regular() && _regular_triang_found) {
	return;
      }
      Options dfs_lexgkz_options(_options);
      dfs_lexgkz_options.remove_options(Options::ONE_FLIP_ONLY
					| Options::REDUCE_POINTS
					| Options::DONT_ADD_POINTS
					| Options::LEX_INCREASING_FLIPS_ONLY
					| Options::LEX_DECREASING_FLIPS_ONLY);

      // generate all flips with no tn-symmetries
      // because those do not leave direction invariant:
      TriangFlips tf(_confdata,
		     dfs_lexgkz_options,
		     tn,
		     symmetryptr_datapair()); // no symmetries since directions are not invariant
      _dfs_lexgkz(tn, _gkz(tn), tf, reverse_order, randomdir);
      if (_regular_triang_found) {
	_new_triangcache.add(tn);
	return;
      }
    }
  }
  
  void SymmetricFlipGraph::_dfs_lexgkz(const TriangNode& tn,
				       const Vector& gkz,
				       const TriangFlips& tf,
				       bool reverse_order,
				       bool randomdir) {

    // recursively try to follow gkz-increasing flips
    // until a regular (target) triangulation is found,
    // thereby ignoring all symmetries:
    if (_cached_triangs.is_cached(tn)) {
      
      // tn has been investigated in a previous path
      // that has already checked all leaves below tn;
      // has one of them been regular, then no further
      // path would have been followed;
      // since tn was rediscovered now,
      // the previous dfs-path had no regular leaf - skip:
      return;
    }
    else {
      _cached_triangs.add(tn);
    }

    // use the cache containing all triangulations connected to a regular triangulation
    // irrespective of direction only if this is used as a heuristic to decide
    // whether the seed is connected to a regular triangulation at all:
    if (_options.stop_at_regularcomp()) {
      if (_global_triangcache.is_cached(tn) || _local_triangcache.is_cached(tn)) {
	
	// tn has been shown to be in a lex-gkz-monotone path
	// to a regular triangulation in a previous investigation:
	MessageStreams::debug() << message::lock
				<< "... worker " << _ID << ": cache hit in recursion of lex-gkz dfs." << std::endl
				<< message::unlock;
	tn.invalidate();
	_regular_triang_found = true;
	return;
      }
    }
    bool found_gkz_monotone_flip(false);
    for (MarkedFlips::const_iterator flipiter = tf.flips().begin();
	 flipiter != tf.flips().end();
	 ++flipiter) {
      const Flip flip(tn, flipiter->first);
      TriangNode new_tn(_symcount, tn, flip);
      if (_options.stop_at_regularcomp()) {
	
	// a triangulation found by *some* flip
	// (only interesting in heuristics for non-monotone flip-connectivity):
	_new_triangcache.add(new_tn); 
      }
      ++_nodecount;
      const Vector new_gkz = _gkz(new_tn);
      if (_is_lexgkz_smaller(gkz, new_gkz, tn, new_tn, reverse_order, randomdir)) {
	
	// a monotone flip has been found;
	// thus, tn is not a leaf in the dfs tree:
	found_gkz_monotone_flip = true;
	  	
	// remove all unwanted flip-generation options:
	Options dfs_lexgkz_options(_options);
	dfs_lexgkz_options.remove_options(Options::ONE_FLIP_ONLY | Options::LEX_INCREASING_FLIPS_ONLY | Options::LEX_DECREASING_FLIPS_ONLY);

	// generate all flips with no tn-symmetries
	// because those do not leave direction invariant:
	TriangFlips new_tf(_confdata,
			   dfs_lexgkz_options,
			   tn, 
			   tf, 
			   new_tn, 
			   flip,
			   symmetryptr_datapair()); // no symmetries since directions are not invariant
	MessageStreams::debug() << message::lock
				<< "... worker " << _ID << ": entering recursion for lexgkz dfs starting at triangulation "
				<< new_tn << " ..." << std::endl
				<< message::unlock;
	++_flipdist;
	report_flip_distclass_progress(MessageStreams::verbose());
	TriangResult new_triangresult(_confdata,
				      new_tn.ID(),
				      _ID,
				      0,
				      0,
				      new_tn);
	if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	  new_triangresult.set_gkzvec(new_gkz);
	}
	new_triangresult.set_flipdist(_flipdist);
	MessageStreams::debug() << message::lock
				<< new_triangresult
				<< std::endl
				<< message::unlock;	  

	// output new_tn if required:
	if (!_options.is_downstream_call()) {
	  _triang_outputter.print_triang(MessageStreams::result(), new_triangresult);
	}

	// for debugging purposes: if new_triangresult is regular,
	// then _regular_triang_found has to be true in this
	// branch of the dfs; in order to double-check the result
	// derived from the leaf, we check regularity here,
	// save the result, and continue; later, we compare
	// this result to the result derived by the leaf of this branch:
	bool debug_by_regularity_check = false;
	if (CommandlineOptions::debug()) {
	  if (!_predicates_checker.check_output_predicates(_confdata, new_triangresult)) {
	    debug_by_regularity_check = true;
	  }
	}
	_dfs_lexgkz(new_tn, new_gkz, new_tf, reverse_order, randomdir);	

	// here we check the result after the branch has returned:
	if (CommandlineOptions::debug()) {
	  if (debug_by_regularity_check && !_regular_triang_found) {
	    MessageStreams::forced() << message::lock
				     << "dfs_lexgkz: a regular triangulation was found:"
				     << '\n'
				     << new_triangresult
				     << '\n'
				     << "is regular, but in the branch below no regular leaf has been found - correcting the result."
				     << std::endl
				     << message::unlock;
	    _regular_triang_found = true;
	    return;
	  }
	}
	if (_regular_triang_found) {

	  // this means, a monotone flip-path
	  // to a regular (target) triangulation has been found;
	  // the previous flip is reported in a flip-sequence:
	  if (CommandlineOptions::output_flipconnectivity()) {
	    MessageStreams::result() << message::lock
				     << flip
				     << std::endl
				     << message::unlock;
	  }
	}
	--_flipdist;
	MessageStreams::debug() << message::lock
				<< "... worker " << _ID << ": done." << std::endl
				<< message::unlock;	
      }
      else {
	MessageStreams::debug() << message::lock
				<< "current gkz " << '\n'
				<< gkz
				<< " non lex-smaller than new gkz" << '\n'
				<< new_gkz << std::endl
				<< " and the characteristic vector of current triang " << tn
				<< " is not lex-smaller than the characteristic vector of new triang " << new_tn
				<< "."
				<< std::endl
				<< message::unlock;
      }
      if (_options.stop_at_regular() && _regular_triang_found) {
      	return;
      }
    }
    if (!found_gkz_monotone_flip) {

      // we are in a leaf and can check now for regularity:
      TriangResult triangresult(_confdata,
				tn.ID(),
				_ID,
				0,
				0,
				tn);
      if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	triangresult.set_gkzvec(gkz);
      }
      triangresult.set_flipdist(_flipdist);
      if (_target_triang_found) {

	// here, the lex-GKZ-maximal regular triangulation is known:
	if (triangresult.triang() == _final_triang) {
	  
	  // the leaf equals the unique optimal regular target,
	  // which is the only option for it to be regular:
	  tn.invalidate();
	  _regular_triang_found = true;
	}
      }
      else {

	// here, a regularity check is necessary:
	if (!_predicates_checker.check_output_predicates(_confdata, triangresult)) {
	
	  tn.invalidate();
	  _regular_triang_found = true;
	  
	  // now, the unique lex-GKZ-maximal regular triangulation is known:
	  _final_triang = triangresult.triang();
	  _target_triang_found = true;
	}
      }
      if (_regular_triang_found) {
	
	// here we have found a regular triangulation
	// in the flip-graph component of seed by dfs, and we are done:
	if (CommandlineOptions::output_flipconnectivity()) {
	  MessageStreams::result() << message::lock;
	  MessageStreams::result() << "... worker " << _ID << ": found a regular triangulation "
				   << std::endl
				   << triangresult
				   << std::endl
				   << "in flip-graph component of "
				   << std::endl
				   << _seed 
				   << std::endl
				   << "by GKZ-";
	  if (randomdir) {
	    MessageStreams::result() << "random-permutation upflips using random permutation "
				     << std::endl
				     << _randperm;
	  }
	  else {
	    if (reverse_order) {
	      MessageStreams::result() << "downflips";
	    }
	    else {
	      MessageStreams::result() << "upflips";
	    }
	  }
	  MessageStreams::result() << std::endl;
	  MessageStreams::result() << "flip list (backwards):"
				   << std::endl;
	  MessageStreams::result() << message::unlock;
	}
      }
      MessageStreams::debug() << message::lock
			      << "leaf triang:\n"
			      << triangresult
			      << "\n(is_regular="
			      << _regular_triang_found
			      // << ")\nwith\n"
			      // << tf.flips().size()
			      // << "\nflips\n"
			      // << tf.flips()
			      << std::endl
			      << message::unlock;
    }
  }

  // search for the GKZ-lexmaximal neighbor of tn;
  // return true if its GKZ is lexlarger than the one of the starting lexmax_tn:
  bool SymmetricFlipGraph::_find_lexgkz_max_neighbor(const TriangNode& tn,
						     const Vector& gkz,
						     const TriangFlips& tf,
						     TriangNode& lexmax_tn,
						     Vector& lexmax_gkz,
						     Flip& lexmax_flip,
						     bool reverse_order,
						     bool randomdir) {
    bool found_gkz_monotone_flip(false);
    MarkedFlips::const_iterator flip_iter = tf.flips().begin();
    while (flip_iter != tf.flips().end()) {
      const Flip flip(tn, flip_iter->first);
      TriangNode new_tn(_symcount, tn, flip);
      if (_options.stop_at_regularcomp()) {
	
	// a triangulation found by *some* flip
	// (only interesting in heuristics for non-monotone flip-connectivity):
	_new_triangcache.add(new_tn); 
      }
      const Vector new_gkz = _gkz(new_tn);
      if (_is_lexgkz_smaller(lexmax_gkz, new_gkz, lexmax_tn, new_tn, reverse_order, randomdir)) {
	found_gkz_monotone_flip = true;
	lexmax_gkz = new_gkz;
	lexmax_tn = new_tn;
	lexmax_flip = flip;
      }
      ++flip_iter;
    }
    return found_gkz_monotone_flip;
  }
 
  // the following is only called whenever "_options.lex_increasing_gkz_only()"
  // and "_options.stop_at_regular()" are both true;
  // this results in following only lex-GKZ increasing flips
  // in the triangflips of a triangnode;
  // the difference to _dfs_lexgkz is that only the greedy flip-path
  // to the GKZ-lex-maximal neighbor is followed:
  void SymmetricFlipGraph::_dive_lexgkz(bool reverse_order,
					bool randomdir) {
    
    // if an earlier investigation has found a regular triangulation,
    // stop immediately:
    if (_options.stop_at_regular() && _regular_triang_found) {
      return;
    }
    if (_options.stop_at_target() && _target_triang_found) {
      return;
    }
    if (_previous_triangs.empty()) {
      MessageStreams::debug() << message::lock
			      << "no seed triangulation present in _previous_triangs - nothing to do."
			      << std::endl
			      << message::unlock;
    }
    else {
      tnode_container_type::const_iterator iter = _previous_triangs.begin();
      _cached_triangs.clear();
      TriangNode tn(iter->first);
      _new_triangcache.add(tn); // this cache holds *all* triangulations found by *any* flip
      ++_nodecount;
      Options dive_lexgkz_options(_options);
      dive_lexgkz_options.remove_options(Options::ONE_FLIP_ONLY
					 | Options::REDUCE_POINTS
					 | Options::DONT_ADD_POINTS
					 | Options::LEX_INCREASING_FLIPS_ONLY
					 | Options::LEX_DECREASING_FLIPS_ONLY);
      
      // generate all flips with no tn-symmetries
      // because those do not leave directions invariant:
      TriangFlips tf(_confdata,
		     dive_lexgkz_options,
		     tn,
		     symmetryptr_datapair()); // no symmetries since directions are not invariant
      _dive_lexgkz(tn, _gkz(tn), tf, reverse_order, randomdir);
      if (_regular_triang_found) {
	_new_triangcache.add(tn);
	return;
      }
    }
  }
  
  void SymmetricFlipGraph::_dive_lexgkz(const TriangNode& tn,
					const Vector& gkz,
					const TriangFlips& tf,
					bool reverse_order,
					bool randomdir) {

    // if an earlier investigation has found a regular/target triangulation,
    // stop immediately:
    if (_options.stop_at_regular() && _regular_triang_found) {
      return;
    }
    if (_options.stop_at_target() && _target_triang_found) {
      return;
    }
    
    // recursively try to follow the gkz-most-increasing flip
    // until a regular (target) triangulation is found,
    // thereby ignoring all symmetries:
    if (_options.stop_at_regularcomp()) {

      // only here use the cache containing all triangulations connected
      // to a regular triangulation irrespective of direction
      // because for the end result directions are irrelevant:
      if (_global_triangcache.is_cached(tn) || _local_triangcache.is_cached(tn)) {
	
	// tn has been shown to be in a lex-gkz-monotone path
	// to a regular triangulation in a previous investigation:
	MessageStreams::debug() << message::lock
				<< "... worker " << _ID << ": cache hit in recursion of lex-gkz dfs." << std::endl
				<< message::unlock;
	tn.invalidate();
	_regular_triang_found = true;
	return;
      }
    }
    TriangNode lexmax_tn(tn);
    Vector lexmax_gkz(gkz);
    Flip lexmax_flip;
    const bool found_gkz_monotone_flip = _find_lexgkz_max_neighbor(tn,
								   gkz,
								   tf,
								   lexmax_tn,
								   lexmax_gkz,
								   lexmax_flip,
								   reverse_order,
								   randomdir);
    if (found_gkz_monotone_flip) {
      ++_nodecount;
      
      // remove all unwanted flip-generation options:
      Options dive_lexgkz_options(_options);
      dive_lexgkz_options.remove_options(Options::ONE_FLIP_ONLY | Options::LEX_INCREASING_FLIPS_ONLY | Options::LEX_DECREASING_FLIPS_ONLY);
      
      // generate all flips with no tn-symmetries
      // because those do not leave direction invariant:
      TriangFlips new_tf(_confdata,
			 dive_lexgkz_options,
			 tn,
			 tf, 
			 lexmax_tn, 
			 lexmax_flip,
			 symmetryptr_datapair()); // no symmetries since directions are not invariant
      ++_flipdist;
      report_flip_distclass_progress(MessageStreams::verbose());
      TriangResult new_triangresult(_confdata,
				    lexmax_tn.ID(),
				    _ID,
				    0,
				    0,
				    lexmax_tn);
      if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	new_triangresult.set_gkzvec(lexmax_gkz);
      }
      new_triangresult.set_flipdist(_flipdist);
      MessageStreams::debug() << message::lock
			      << new_triangresult
			      << std::endl
			      << message::unlock;
      
      if (!_predicates_checker.check_output_predicates(_confdata, new_triangresult)) {

	// we flipped to a not non-regular triangulation, and we are done:
	if (_options.stop_at_regular() && new_triangresult.is_regular()) {
	  _regular_triang_found = true;
	}
	if (_options.stop_at_target() && new_triangresult.is_targetequiv()) {
	  _target_triang_found = true;
	}
	if (CommandlineOptions::output_flipconnectivity()) {
	  MessageStreams::result() << message::lock
				   << "non-regular seed triangulation" << '\n'
				   << _seed << '\n'
				   << "could be monotonically flipped w.r.t. permutation" << '\n'
				   << _randperm << '\n'
				   << "to regular triangulation" << '\n'
				   << new_triangresult << '\n'
				   << "flip-list (backwards):"
				   << std::endl
				   << message::unlock;
	}
      }
      else {
      
	// otherwise, output new_triangresult if required:
	if (!_options.is_downstream_call()) {
	  _triang_outputter.print_triang(MessageStreams::result(), new_triangresult);
	}
	MessageStreams::debug() << message::lock
				<< "... worker " << _ID << ": entering recursion for lexgkz dive starting at triangulation "
				<< lexmax_tn << " ..." << std::endl
				<< message::unlock;
	
	// extend the flip-path:
	_dive_lexgkz(lexmax_tn, lexmax_gkz, new_tf, reverse_order, randomdir);
      }
      
      // check result:
      if (_regular_triang_found || _target_triang_found) {
	
	// this means, a monotone flip-path
	// to a regular (target) triangulation has been found;
	// the previous flip is reported in a flip-sequence:
	if (CommandlineOptions::output_flipconnectivity()) {
	  MessageStreams::result() << message::lock
				   << lexmax_flip
				   << std::endl
				   << message::unlock;
	}
      }
      --_flipdist;
      MessageStreams::debug() << message::lock
			      << "... worker " << _ID << ": done." << std::endl
			      << message::unlock;	
    }
  }

  void SymmetricFlipGraph::_dive_to_opt_lexgkz(bool reverse_order,
					       bool randomdir) {
     
    if (_previous_triangs.empty()) {
      MessageStreams::debug() << message::lock
			      << "no seed triangulation present in _previous_triangs - nothing to do."
			      << std::endl
			      << message::unlock;
    }
    else {
      tnode_container_type::const_iterator iter = _previous_triangs.begin();
      _cached_triangs.clear();
      TriangNode tn(iter->first);
      _new_triangcache.add(tn); // this cache holds *all* triangulations found by *any* flip
      ++_nodecount;
      Options dive_to_opt_options(_options);
      dive_to_opt_options.remove_options(Options::ONE_FLIP_ONLY
					 | Options::REDUCE_POINTS
					 | Options::DONT_ADD_POINTS
					 | Options::LEX_INCREASING_FLIPS_ONLY
					 | Options::LEX_DECREASING_FLIPS_ONLY);
      
      // generate all flips with no tn-symmetries
      // because those do not leave directions invariant:
      TriangFlips tf(_confdata,
		     dive_to_opt_options,
		     tn,
		     symmetryptr_datapair()); // no symmetries since directions are not invariant
      _dive_to_opt_lexgkz(tn, _gkz(tn), tf, reverse_order, randomdir);
    }
  }

  void SymmetricFlipGraph::_dive_to_opt_lexgkz(const TriangNode& tn,
					       const Vector& gkz,
					       const TriangFlips& tf,
					       bool reverse_order,
					       bool randomdir) {

    TriangNode lexmax_tn(tn);
    Vector lexmax_gkz(gkz);
    Flip lexmax_flip;
    const bool found_gkz_monotone_flip = _find_lexgkz_max_neighbor(tn,
								   gkz,
								   tf,
								   lexmax_tn,
								   lexmax_gkz,
								   lexmax_flip,
								   reverse_order,
								   randomdir);
    if (found_gkz_monotone_flip) {
      ++_nodecount;
      
      // remove all unwanted flip-generation options:
      Options dive_lexgkz_options(_options);
      dive_lexgkz_options.remove_options(Options::ONE_FLIP_ONLY | Options::LEX_INCREASING_FLIPS_ONLY | Options::LEX_DECREASING_FLIPS_ONLY);
            
      // generate all flips with no tn-symmetries
      // because those do not leave direction invariant:
      TriangFlips new_tf(_confdata,
			 dive_lexgkz_options,
			 tn,
			 tf, 
			 lexmax_tn, 
			 lexmax_flip,
			 symmetryptr_datapair()); // no symmetries since directions are not invariant
      MessageStreams::debug() << message::lock
			      << "... worker " << _ID << ": entering recursion for lexgkz diving starting at triangulation "
			      << lexmax_tn << " ..." << std::endl
			      << message::unlock;
      TriangResult new_triangresult(_confdata,
				    lexmax_tn.ID(),
				    _ID,
				    0,
				    0,
				    lexmax_tn);
      if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	new_triangresult.set_gkzvec(lexmax_gkz);
      }
      MessageStreams::debug() << message::lock
			      << new_triangresult
			      << std::endl
			      << message::unlock;
      
      // extend the flip-path:
      _dive_to_opt_lexgkz(lexmax_tn, lexmax_gkz, new_tf, reverse_order, randomdir);
    }
    else {
      _final_triang = tn;
    }
  }

  bool SymmetricFlipGraph::_is_lex_smaller(const SimplicialComplex& triang,
					   const SimplicialComplex& new_triang,
					   const bool reverse_order,
					   const bool randomdir) {
    if (randomdir) {

      // this takes some care:
      std::set<Simplex> auxtriang1;
      std::set<Simplex> auxtriang2;
      Symmetry auxsym(_randperm, false);
      for (SimplicialComplex::const_iterator iter1 = triang.begin();
	   iter1 != triang.end();
	   ++iter1) {
	auxtriang1.insert(auxsym.map(*iter1));
      }
      for (SimplicialComplex::const_iterator iter2 = new_triang.begin();
	   iter2 != new_triang.end();
	   ++iter2) {
	auxtriang2.insert(auxsym.map(*iter2));
      }

      // from now on, auxtriang1 and auxtriang2 are triang and new_triang
      // renumbered by auxsym and ordered according to "operator<" on simplices:
      std::set<Simplex>::const_iterator auxiter1 = auxtriang1.begin();
      std::set<Simplex>::const_iterator auxiter2 = auxtriang2.begin();
      while (true) {

	if ((auxiter1 != auxtriang1.end()) && auxiter2 == auxtriang2.end()) {

	  // equal so far, but auxtriang1 has more simplices:
	  if (reverse_order) {
	    return true;
	  }
	  else {
	    return false;
	  }
	}
	else if ((auxiter2 != auxtriang2.end()) && auxiter1 == auxtriang1.end()) {

	  // equal so far, but auxtriang2 has more simplices:
	  if (reverse_order) {
	    return false;
	  }
	  else {
	    return true;
	  }
	}
	else if ((auxiter1 == auxtriang1.end()) && auxiter2 == auxtriang2.end()) {

	  // equal to the end - neither is lex-smaller:
	  return false;
	}

	// from now on, both have an additional simplex:
	if (auxiter1->lexsmaller(*auxiter2)) {

	  // the next simplex is lex-smaller in auxtriang1:
	  if (reverse_order) {
	    return false;
	  }
	  else {
	    return true;
	  }
	}
	if (auxiter2->lexsmaller(*auxiter1)) {

	  // the next simplex is lex-smaller in auxtriang2:
	  if (reverse_order) {
	    return true;
	  }
	  else {
	    return false;
	  }
	}

	// from now on both are equal up to the iterator position - continue:
	++auxiter1;
	++auxiter2;
      }
    }
    else {
      return (
	      (!reverse_order
	       &&
	       triang.lexsmaller(new_triang, triang.rank())
	       )
	      ||
	      (reverse_order
	       &&
	       triang.lexgreater(new_triang, triang.rank())
	       )
	      );
    }
  }
  
  // search for the lexmaximal neighbor of tn;
  // return true if it is lexlarger than the starting lexmax_tn:
  bool SymmetricFlipGraph::_find_lex_max_neighbor(const TriangNode& tn,
						  const TriangFlips& tf,
						  TriangNode& lexmax_tn,
						  Flip& lexmax_flip,
						  bool reverse_order,
						  bool randomdir) {
    bool found_lex_monotone_flip(false);
    MarkedFlips::const_iterator flip_iter = tf.flips().begin();
    while (flip_iter != tf.flips().end()) {
      const Flip flip(tn, flip_iter->first);
      TriangNode new_tn(_symcount, tn, flip);
      if (_options.stop_at_regularcomp()) {
	
	// a triangulation found by *some* flip
	// (only interesting in heuristics for non-monotone flip-connectivity):
	_new_triangcache.add(new_tn); 
      }
      if (_is_lex_smaller(lexmax_tn, new_tn, reverse_order, randomdir)) {
	found_lex_monotone_flip = true;
	lexmax_tn = new_tn;
	lexmax_flip = flip;
      }
      ++flip_iter;
    }
    return found_lex_monotone_flip;
  }
  
  void SymmetricFlipGraph::_dive_lexflip(bool reverse_order,
					 bool randomdir) {
    if (_options.stop_at_regular() && _regular_triang_found) {
      return;
    }
    if (_options.stop_at_target() && _target_triang_found) {
      return;
    }
    if (!_previous_triangs.empty()) {
      tnode_container_type::const_iterator iter = _previous_triangs.begin();
      TriangNode tn(iter->first);
      if (CommandlineOptions::output_gkzvecs() && _confdata.has_voltable()) {
	_current_gkz = _gkz(tn);
      }
      Options dive_lexflip_options(_options);
      if (randomdir) {
	dive_lexflip_options.remove_options(Options::LEX_INCREASING_FLIPS_ONLY
					   | Options::LEX_DECREASING_FLIPS_ONLY);
      }
      else {
	if (reverse_order) {
	  dive_lexflip_options.add_options(Options::LEX_DECREASING_FLIPS_ONLY | Options::ONE_FLIP_ONLY);
	  dive_lexflip_options.remove_options(Options::LEX_INCREASING_FLIPS_ONLY);
	}
	else {
	  dive_lexflip_options.add_options(Options::LEX_INCREASING_FLIPS_ONLY | Options::ONE_FLIP_ONLY);
	  dive_lexflip_options.remove_options(Options::LEX_DECREASING_FLIPS_ONLY);
	}
      }
      TriangFlips tf(_confdata,
		     dive_lexflip_options,
		     tn, 
		     symmetryptr_datapair());
      TriangResult triangresult(_confdata,
				tn.ID(),
				_ID,
				0,
				0,
				tn);
      if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	triangresult.make_gkzvec();
      }
      triangresult.set_flipdist(_flipdist);
      if (!_predicates_checker.check_output_predicates(_confdata, triangresult)) {

	// we flipped to a not non-regular triangulation, and we are done:
	if (_options.stop_at_regular() && triangresult.is_regular()) {
	  _regular_triang_found = true;
	}
	if (_options.stop_at_target() && triangresult.is_targetequiv()) {
	  _target_triang_found = true;
	}
	tn.invalidate();
	  
	// here we have found a regular triangulation
	// in the flip-graph component of seed by diving, and we are done:
	if (CommandlineOptions::output_flipconnectivity()) {
	  MessageStreams::result() << message::lock
				   << "... worker " << _ID << ": triangulation "
				   << triangresult
				   << " is regular itself, thus in the regular flip-graph component" << std::endl
				   << message::unlock;
	}
	
	// here we have found a regular triangulation
	// in the flip-graph component of seed by diving, and we are done:
	_regular_triang_found = true;
      }
      _dive_lexflip(tn, tf, reverse_order);
    }
    else {
      MessageStreams::debug() << message::lock
			      << "no seed triangulation present in _previous_triangs - nothing to do."
			      << std::endl
			      << message::unlock;
    }
  }

  // the following is only called whenever "_options.lexflip_dive_only()"
  // and "_options.stop_at_regular()" are both true;
  // this results in only lex-flips in the triangflips of a triangnode:
  void SymmetricFlipGraph::_dive_lexflip(const TriangNode& tn,
					 const TriangFlips& tf,
					 bool reverse_order,
					 bool randomdir) {

    // recursively try to follow lex-increasing flips until a target or a regular triangulation is found,
    // thereby ignoring all symmetries:
    if (_options.stop_at_regular() && _regular_triang_found) {
      return;
    }
    MarkedFlips::const_iterator flipiter = tf.flips().begin();
    if (flipiter == tf.flips().end()) {

      // report that there is no monotone flip in tn:
      if (reverse_order) {
	MessageStreams::debug() << message::lock
	  << "... worker " << this->_ID << ": non-regular triangulation " << static_cast<const SimplicialComplex&>(tn)
	  << " has no lex-decreasing flip!" << std::endl
	  << message::unlock;
      }
	else {
	  MessageStreams::debug() << message::lock
	    << "... worker " << this->_ID << ": non-regular triangulation " << static_cast<const SimplicialComplex&>(tn)
	    << " has no lex-increasing flip!" << std::endl
	    << message::unlock;
      }
    }
    else {
      const Flip flip(tn, flipiter->first);
      TriangNode new_tn(_symcount, tn, flip);
      ++_flipdist;

      if (_options.stop_at_regularcomp()) {
	
	// use the cache containing all triangulations connected to a regular triangulation
	// irrespective of direction only if this is used as a heuristic to decide
	// whether the seed is connected to a regular triangulation at all:
	if (_global_triangcache.is_cached(new_tn) || _local_triangcache.is_cached(new_tn)) {
	  MessageStreams::debug() << message::lock
				  << "... worker " << _ID << ": cache hit in lex-flip dive." << std::endl
				  << message::unlock;
	  _regular_triang_found = true;
	  return;
	}
      }
      TriangResult triangresult(_confdata,
				new_tn.ID(),
				_ID,
				0,
				0,
				new_tn);
      if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	triangresult.make_gkzvec();
      }
      triangresult.set_flipdist(_flipdist);
      if (!_predicates_checker.check_output_predicates(_confdata, triangresult)) {
	
	// we flipped to a not non-regular triangulation, and we are done:
	if (_options.stop_at_regular() && triangresult.is_regular()) {
	  _regular_triang_found = true;
	}
	if (_options.stop_at_target() && triangresult.is_targetequiv()) {
	  _target_triang_found = true;
	}
	new_tn.invalidate();
	
	// here we have found a regular triangulation
	// in the flip-graph component of seed by diving, and we are done:
	if (CommandlineOptions::output_flipconnectivity()) {
	  MessageStreams::result() << message::lock;
	  MessageStreams::result() << "... worker " << _ID << ": found regular triangulation "
				   << std::endl
				   << triangresult
				   << " in flip-graph component of "
				   << std::endl
				   << _seed
				   << std::endl
				   << "by lex-";
	  if (reverse_order) {
	    MessageStreams::result() << "downflips";
	  }
	  else {
	    MessageStreams::result() << "upflips";
	  }
	  MessageStreams::result() << std::endl
				   << "flip list (backwards):"
				   << std::endl
				   << flip
				   << std::endl;
	  MessageStreams::result() << message::unlock;
	}
	_regular_triang_found = true;
      }
      else {
	
	// output new_tn if required:
	if (!_options.is_downstream_call()) {
	  _triang_outputter.print_triang(MessageStreams::result(), triangresult);
	}
	
	// here, we continue diving
	// (stop at first lex-increasing flip because only one is followed anyway),
	// and generate only one lex-increasing flip,
	// which can be checked by the flip generator:
	Options dive_lexflip_options(_options);
	if (randomdir) {
	  dive_lexflip_options.remove_options(Options::LEX_INCREASING_FLIPS_ONLY
					     | Options::LEX_DECREASING_FLIPS_ONLY);
	}
	else {
	  if (reverse_order) {
	    dive_lexflip_options.add_options(Options::LEX_DECREASING_FLIPS_ONLY | Options::ONE_FLIP_ONLY);
	    dive_lexflip_options.remove_options(Options::LEX_INCREASING_FLIPS_ONLY);
	  }
	  else {
	    dive_lexflip_options.add_options(Options::LEX_INCREASING_FLIPS_ONLY | Options::ONE_FLIP_ONLY);
	    dive_lexflip_options.remove_options(Options::LEX_DECREASING_FLIPS_ONLY);
	  }
	}
	TriangFlips new_tf(_confdata,
			   dive_lexflip_options,
			   tn, 
			   tf, 
			   new_tn, 
			   flip,
			   symmetryptr_datapair());
	_new_triangcache.add(new_tn);
	MessageStreams::debug() << message::lock
				<< "... worker " << _ID << ": entering recursion for lexflip diving starting at triangulation "
				<< new_tn << " ..." << std::endl
				<< message::unlock;
	_dive_lexflip(new_tn, new_tf, reverse_order);
	if (_regular_triang_found) {
	  if (CommandlineOptions::output_flipconnectivity()) {
	    MessageStreams::result() << message::lock
				     << flip
				     << std::endl
				     << message::unlock;
	  }
	}
	MessageStreams::debug() << message::lock
				<< "... worker " << _ID << ": done." << std::endl
				<< message::unlock;	
      }
    }
  }
  
  void SymmetricFlipGraph::_dive_to_opt_lexflip(bool reverse_order,
						bool randomdir) {
     
    if (_previous_triangs.empty()) {
      MessageStreams::debug() << message::lock
			      << "no seed triangulation present in _previous_triangs - nothing to do."
			      << std::endl
			      << message::unlock;
    }
    else {
      tnode_container_type::const_iterator iter = _previous_triangs.begin();
      _cached_triangs.clear();
      TriangNode tn(iter->first);
      _new_triangcache.add(tn); // this cache holds *all* triangulations found by *any* flip
      ++_nodecount;
      Options dive_to_opt_options(_options);
      dive_to_opt_options.remove_options(Options::ONE_FLIP_ONLY
					 | Options::REDUCE_POINTS
					 | Options::DONT_ADD_POINTS);
      if (randomdir) {
	dive_to_opt_options.remove_options(Options::LEX_INCREASING_FLIPS_ONLY
					   | Options::LEX_DECREASING_FLIPS_ONLY);
      }
      else {
	if (reverse_order) {
	  dive_to_opt_options.remove_options(Options::LEX_INCREASING_FLIPS_ONLY);
	  dive_to_opt_options.add_options(Options::LEX_DECREASING_FLIPS_ONLY);
	}
	else {
	  dive_to_opt_options.remove_options(Options::LEX_DECREASING_FLIPS_ONLY);
	  dive_to_opt_options.add_options(Options::LEX_INCREASING_FLIPS_ONLY);
	}
      }
      
      // generate all flips with no tn-symmetries
      // because those do not leave directions invariant:
      TriangFlips tf(_confdata,
		     dive_to_opt_options,
		     tn,
		     symmetryptr_datapair()); // no symmetries since directions are not invariant
      _dive_to_opt_lexflip(tn, tf, reverse_order, randomdir);
    }
  }

  void SymmetricFlipGraph::_dive_to_opt_lexflip(const TriangNode& tn,
						const TriangFlips& tf,
						bool reverse_order,
						bool randomdir) {

    TriangNode lexmax_tn(tn);
    Flip lexmax_flip;
    const bool found_lex_monotone_flip = _find_lex_max_neighbor(tn,
								tf,
								lexmax_tn,
								lexmax_flip,
								reverse_order,
								randomdir);
    if (found_lex_monotone_flip) {
      ++_nodecount;
      
      // remove all unwanted flip-generation options:
      Options dive_lexflip_options(_options);
      if (randomdir) {
	dive_lexflip_options.remove_options(Options::LEX_INCREASING_FLIPS_ONLY
					    | Options::LEX_DECREASING_FLIPS_ONLY);
      }
      else {
	if (reverse_order) {
	  dive_lexflip_options.remove_options(Options::LEX_INCREASING_FLIPS_ONLY);
	  dive_lexflip_options.add_options(Options::LEX_DECREASING_FLIPS_ONLY);
	}
	else {
	  dive_lexflip_options.remove_options(Options::LEX_DECREASING_FLIPS_ONLY);
	  dive_lexflip_options.add_options(Options::LEX_INCREASING_FLIPS_ONLY);
	}
      }
      
      // generate all flips with no tn-symmetries
      // because those do not leave direction invariant:
      TriangFlips new_tf(_confdata,
			 dive_lexflip_options,
			 tn,
			 tf, 
			 lexmax_tn, 
			 lexmax_flip,
			 symmetryptr_datapair()); // no symmetries since directions are not invariant
      MessageStreams::debug() << message::lock
			      << "... worker " << _ID << ": entering recursion for lexflip diving starting at triangulation "
			      << lexmax_tn << " ..." << std::endl
			      << message::unlock;
      TriangResult new_triangresult(_confdata,
				    lexmax_tn.ID(),
				    _ID,
				    0,
				    0,
				    lexmax_tn);
      if (CommandlineOptions::output_gkzvecs() && _confdata.has_points()) {
	new_triangresult.make_gkzvec();
      }
      MessageStreams::debug() << message::lock
			      << new_triangresult
			      << std::endl
			      << message::unlock;
      
      // extend the flip-path:
      _dive_to_opt_lexflip(lexmax_tn, new_tf, reverse_order, randomdir);
    }
    else {
      _final_triang = tn;
    }
  }

  void SymmetricFlipGraph::_init_symmetry_workers() {
    int thread_no(0);
    for (points_simpidx_symmetries_type::const_iterator iter = _active_points_simpidx_symmetries.begin();
	 iter != _active_points_simpidx_symmetries.end();
	 ++iter) {
      
      // distribute all simpidx symmetries in a round-robin fashion:
      // generate a copy of each symmetry by assigning to a non-constant
      // temporary object for thread-safety:
      const points_simpidx_symmetry_type sympair(*iter);
      _worker_symmetries[thread_no].push_back(sympair);
      thread_no = (thread_no + 1) % _no_of_threads;
    }
    for (int i = 0; i < _no_of_threads; ++i) {
      MessageStreams::debug() << message::lock
			      << "initializing worker " << i << " with symmetries "
			      << _worker_symmetries[i] << " ..." << std::endl
			      << message::unlock;
      _symmetry_workers.emplace_back(i, *this, _worker_symmetries[i]);
      MessageStreams::debug() << message::lock
			      << "... done" << std::endl
			      << message::unlock;
    }
    for (int i = 0; i < _no_of_threads; ++i) {
      MessageStreams::verbose() << message::lock
				<< "starting thread " << i << " ..." << std::endl
				<< message::unlock;
      _threads.push_back(std::thread(&SymmetricFlipGraph::SymmetryWorker::operator(),
				     &_symmetry_workers[i]));

      // for the time being, allow the worker to wait:
      MessageStreams::verbose() << message::lock
				<< "... done" << std::endl
				<< message::unlock;
    }
  }
  
  void SymmetricFlipGraph::_init() {
    if (CommandlineOptions::parallel_symmetries()) {
      _init_symmetry_workers();
    }
    if (_options.output_triangs()) {
      if (CommandlineOptions::debug()) {
	_triang_outputter.activate_debug();
      }
      else {
	_triang_outputter.activate();
      }
    }
    if (CommandlineOptions::output_flips()) {
      if (CommandlineOptions::debug()) {
	_flip_outputter.activate_debug();
      }
      else {
	_flip_outputter.activate();
      }
    }

    const LabelSet seed_support(_seed.support());
    const parameter_type seed_support_card(seed_support.card());

    // initialize special triangulations and parameters that are updated
    // during the exploration of the flip-graph:
    _final_triang = _seed;
    _min_support_triang = _seed;
    _min_support_card = seed_support_card;
    _min_card_triang = _seed;
    _min_card = _seed.card();

    if (_options.stop_at_invalid()) {

      // here we stop if a single invalid triangulation has been found;
      // thus, only selected options are observed

      if (_options.stop_at_regular()) {

	if (_options.dont_add_points()) {
	  
	  // to save some work, never add points that are unused in seed:
	  _predicates_checker.add_search_predicate(new PredicatesChecker::search_no_insertion_type(seed_support_card));
	}	
	if (_options.reduce_points()) {
	  
	  // if more points can be flipped-out, do so and never again accept triangulations using this point:
	  _predicates_checker.add_search_predicate(new PredicatesChecker::search_min_no_of_verts_type(seed_support_card));
	}

	// here we stop if a regular triangulation has been found (slow, therefore the last predicate):
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_checknonregularity_type());
      }
      if (_options.stop_at_target()) {
	
	// here we stop if a triangulation in the orbit
	// of the target triangulation has been found:
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_checknontargetequiv_type(_target,
													_active_symmetries));
      }
    }
    else {
    
      // collect the additional checks on output triangulations (exploration continues also at infeasible nodes):
      if (CommandlineOptions::max_no_of_simplices() > 0) {
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_checkmaxno_type());
      }
      if (CommandlineOptions::require_point()) {
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_checkpoint_type());
      }
      if (CommandlineOptions::observe_required_symmetries()) {
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_checksymmetries_type());
      }
      if (CommandlineOptions::no_of_simplices() > 0) {
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_checkno_type());
      }
      if (!_options.no_regularity_checks() && CommandlineOptions::check_nonregular()) {
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_checknonregularity_type());
      }
      if (CommandlineOptions::check_unimodular()) {
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_checkunimodularity_type());
      }
      if (!_options.no_regularity_checks() && CommandlineOptions::check_findregular()) {
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_findregular_type());
      }
      if (!_options.no_regularity_checks() && CommandlineOptions::check_randomfindregular()) {
	_predicates_checker.add_output_predicate(new PredicatesChecker::output_findregular_type());
      }
      
      // collect the additional checks on seaerched triangulations (exploration stops at infeasible nodes):
      if (!_options.no_regularity_checks() && CommandlineOptions::check_regular()) {
	_predicates_checker.add_search_predicate(new PredicatesChecker::search_checkreg_type());
      }
      if (_options.dont_add_points()) {
	_predicates_checker.add_search_predicate(new PredicatesChecker::search_no_insertion_type(seed_support_card));
      }
      if (_options.reduce_points()) {
	_predicates_checker.add_search_predicate(new PredicatesChecker::search_min_no_of_verts_type(seed_support_card));
      }
    }
    
    if (CommandlineOptions::read_status()) {
      std::ifstream read_str(CommandlineOptions::read_file());
      MessageStreams::verbose() << message::lock
				<< "initializing from file " << CommandlineOptions::read_file() << " ..." << std::endl
				<< message::unlock;
      read(read_str);      
      MessageStreams::verbose() << message::lock
				<< "... done" << std::endl
				<< message::unlock;
      MessageStreams::debug() << message::lock
			      << "read data:" 
			      << std::endl
			      << *this
			      << std::endl
			      << message::unlock;
    }
    else {
      const TriangNode current_triang = TriangNode(_symcount,
						   _confdata.no(),
						   _confdata.rank(),
						   _seed);
      MessageStreams::debug() << message::lock
			      << "seed node: " << current_triang
			      << std::endl
			      << message::unlock;
      if (CommandlineOptions::use_gkz() && _confdata.has_points()) {
	_current_gkz = std::move(_gkz(current_triang));
      }
      _node_symmetryptrs = _stabilizer_ptrs(current_triang, _current_gkz);
      TriangResult triangresult(_confdata,
				current_triang.ID(),
				_ID,
				0,
				0,
				current_triang);
	
      // in the following function, _regular_triang_found is set to true
      // if the orbit of current_triang contains a regular triangulation;
      // likewise, _target_triang_found is set to true,
      // if the orbit of current_triang contains a triangulation equivalent to target:
      _old_symmetry_class(current_triang, _current_gkz);
      if ((_options.stop_at_regular() && _regular_triang_found)
	  || (_options.stop_at_target() && _target_triang_found)) {
	return;
      }
      
      // from here, it is known that the orbit of seed contains no
      // triangulation that would cause a stop:
      if (_search_orbitsize > 0) {

	// at least one triangulation in the orbit has all search predicates,
	// and a further exploration is needed:
	if (_output_orbitsize > 0) {

	  // at least one triangulation in the orbit has all output predicates,
	  // and the counts have to be increased:
	  ++_symcount;
	  _totalcount += _output_orbitsize;
	}
	const TriangFlips current_flips = TriangFlips(_confdata,
						      _options,
						      current_triang, 
						      _node_symmetryptrs);

	// initialize the seed for the bfs:
	_previous_triangs.insert(std::pair<TriangNode, TriangFlips>(current_triang, current_flips));
	++_storedcount;
#ifdef CHECK_NEW
	_all_triangs.insert(std::pair<TriangNode, TriangFlips>(current_triang, current_flips));	
#endif
	_stabilizers.insert(std::pair<TriangNode, symmetryptr_datapair>(current_triang, _node_symmetryptrs));
	if (CommandlineOptions::use_gkz() && _confdata.has_voltable()) {
	  _known_gkzs.insert(_current_gkz);
	}
      }
    }
    report_flip_distclass_progress(MessageStreams::verbose());
    if (_options.lexflip_dive_opt_only()) {
      
      // this option must be checked first:
      // we greedily flip w.r.t. to a sink
      // with directions from the other options:
      MessageStreams::debug() << message::lock
			      << "lex-dive-to-opt ..." << std::endl
			      << message::unlock;
      _dive_to_opt_lexflip(_options.lexflip_dive_down_only(), _options.lexflip_dive_randdir_only());
      MessageStreams::debug() << message::lock
			      << "worker " << _ID << ":\n"
			      << "seed triangulation " << _seed << '\n'
			      << " was greedily monotonically flipped to locally optimal triangulation " << '\n'
			      << _final_triang
			      << std::endl
			      << message::unlock;
      MessageStreams::debug() << message::lock
			      << "... done." << std::endl
			      << message::unlock;
    }
    else if (_options.lexgkz_dive_opt_only()) {
      
      // this option must be checked first:
      // we greedily flip w.r.t. to a sink
      // with directions from the other options:
      MessageStreams::debug() << message::lock
			      << "gkz-dive-to-opt ..." << std::endl
			      << message::unlock;
      _dive_to_opt_lexgkz(_options.lexgkz_dive_down_only(), _options.lexgkz_dive_randdir_only());
      MessageStreams::debug() << message::lock
			      << "worker " << _ID << ":\n"
			      << "seed triangulation " << _seed << '\n'
			      << " was greedily monotonically flipped to locally optimal triangulation " << '\n'
			      << _final_triang
			      << std::endl
			      << message::unlock;
      MessageStreams::debug() << message::lock
			      << "... done." << std::endl
			      << message::unlock;
    }
    else if (_options.lexflip_dive_up_only()
	     || _options.lexflip_dive_down_only()
	     || _options.lexflip_dive_randdir_only()
	     || _options.lexgkz_dive_up_only()
	     || _options.lexgkz_dive_down_only()
	     || _options.lexgkz_dive_randdir_only()) {
      
      // here, we look for a target triangulation or a regular triangulation by
      // greedily flipping monotonically in a direction:
      if (_options.lexflip_dive_up_only()) {
	
	// here, we only dive up towards a regular triangulation
	// with the fast-to-evaluate lex-flip direction:
	MessageStreams::debug() << message::lock
				<< "lex-dive-up heuristics ..." << std::endl
				<< message::unlock;
	_dive_lexflip();
	MessageStreams::debug() << message::lock
				<< "... done." << std::endl
				<< message::unlock;
      }
      if (_options.lexflip_dive_down_only()) {
	
      // here, we only dive down towards a regular triangulation
      // with the fast-to-evaluate lex-flip direction:
	MessageStreams::debug() << message::lock
				<< "lex-dive-down heuristics ..." << std::endl
				<< message::unlock;
	_dive_lexflip(true);
	MessageStreams::debug() << message::lock
				<< "... done." << std::endl
				<< message::unlock;
      } 
      if (_options.lexgkz_dive_up_only()) {
	
	// here, we dive up towards a regular triangulation
	// with the slower lex-gkz up direction:
	MessageStreams::debug() << message::lock
				<< "gkz-dive-up ..." << std::endl
				<< message::unlock;
	_dive_lexgkz(false, false);
	if (!_regular_triang_found) {
	  MessageStreams::debug() << message::lock
				  << "worker " << _ID << ": " << '\n'
				  << "seed triangulation " << _seed << '\n'
				  << " is not sub-regular w.r.t. upflips." << std::endl
				  << message::unlock;
	}
	MessageStreams::debug() << message::lock
				<< "... done." << std::endl
				<< message::unlock;
      }
      if (_options.lexgkz_dive_down_only()) {
	
	// here, we dive down towards a regular triangulation
	// with the slower lex-gkz down direction:
	MessageStreams::debug() << message::lock
				<< "gkz-dive-down ..." << std::endl
				<< message::unlock;
	_dive_lexgkz(true, false);
	if (!_regular_triang_found) {
	  MessageStreams::debug() << message::lock
				  << "worker " << _ID << ": " << '\n'
				  << "seed triangulation " << _seed << '\n'
				  << " is not sup-regular, i.e., not sub-regular w.r.t. downflips." << std::endl
				  << message::unlock;
	}
	MessageStreams::debug() << message::lock
				<< "... done." << std::endl
				<< message::unlock;
      }
      if (_options.lexgkz_dive_randdir_only()) {
      
	// here, we dive in lex direction w.r.t. a random permutation towards a regular triangulation:
	MessageStreams::debug() << message::lock
				<< "gkz-dive-randdir ..." << std::endl
				<< message::unlock;
	_dive_lexgkz(false, true);
	if (!_regular_triang_found) {
	  MessageStreams::debug() << message::lock
				  << "worker " << _ID << ": " << '\n'
				  << "seed triangulation " << _seed << '\n'
				  << " is possibly a not-regular-component triangulation." << std::endl
				  << message::unlock;
	}
	MessageStreams::debug() << message::lock
				<< "... done." << std::endl
				<< message::unlock;
      }
    }
    else {

      // here, we explore the whole flip graph up to symmetry:
      _bfs();
    }
  }

  // stream input:
  std::istream& SymmetricFlipGraph::read(std::istream& ist) {
    std::string dump_line;
    while ((std::getline(ist, dump_line))) {
      std::string::size_type lastPos = dump_line.find_first_not_of(" ", 0);
      std::string::size_type pos     = dump_line.find_first_of(" ", lastPos);
      std::string keyword            = dump_line.substr(lastPos, pos - lastPos);

      // first, some data is parsed that makes sure that the dumped computational results were
      // obtained with the "right" data:
      if (keyword == "_dump_no") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _dump_no)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _dump_no; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "_dump_no initialized with " << _dump_no << std::endl
				<< message::unlock;
	_checkpoint.set_dump_no(_dump_no + 1);
      }
      if (keyword == "_no") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	parameter_type no_check;
      
	if (!(istrst >> no_check)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _no; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	if (_confdata.no() != no_check) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): no of points in input differs from no of points in dump file; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::forced() << message::lock
				 << "no of points in input coincides with no of points in dump file: okay" << std::endl
				 << message::unlock;
      }
      if (keyword == "_rank") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	parameter_type rank_check;

	if (!(istrst >> rank_check)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _rank; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	if (_confdata.rank() != rank_check) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): rank of input differs from rank in dump file; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::forced() << message::lock
				 << "rank of input coincides with rank of dump file: okay" << std::endl
				 << message::unlock;
      }
      if (_confdata.has_points()) {
	if (keyword == "_points") {
	  lastPos = dump_line.find_first_not_of(" ", pos);
	  std::string value = dump_line.substr(lastPos, dump_line.length());
	  std::istringstream istrst (value, std::ios::in);
	  PointConfiguration points_check;
	
	  if (!(istrst >> points_check)) {
	    MessageStreams::forced() << message::lock
				     << "SymmetricFlipGraph::read(std::istream& ist): error while reading _points; exiting" << std::endl
				     << message::unlock;
	    exit(1);
	  }
	  if (_confdata.points() != points_check) {
	    MessageStreams::forced() << message::lock
				     << "SymmetricFlipGraph::read(std::istream& ist): points of input differ from points in dump file; exiting" << std::endl
				     << message::unlock;
	    exit(1);
	  }
	  MessageStreams::forced() << message::lock
				   << "points of input coincide with points in dump file: okay" << std::endl
				   << message::unlock;
	}
      }
      if (_confdata.has_chiro()) {
	if (keyword == "_chiro") {
	  lastPos = dump_line.find_first_not_of(" ", pos);
	  std::string value = dump_line.substr(lastPos, dump_line.length());
	  std::istringstream istrst (value, std::ios::in);
	  Chirotope chiro_check(_confdata.chiro());	
	  if (!(istrst >> chiro_check)) {
	    MessageStreams::forced() << message::lock
				     << "SymmetricFlipGraph::read(std::istream& ist): error while reading _chiro; exiting" << std::endl
				     << message::unlock;
	    exit(1);
	  }
	  if (_confdata.chiro() != chiro_check) {
	    MessageStreams::forced() << message::lock
				     << "SymmetricFlipGraph::read(std::istream& ist): chirotope of input differs from chirotope in dump file; exiting" << std::endl
				     << message::unlock;
	    exit(1);
	  }
	  MessageStreams::debug() << message::lock
				  << "chirotope of input coincides with chirotope of dump file: okay" << std::endl
				  << message::unlock;
	}
      }
      if (keyword == "_symmetries") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	SymmetryGroup symmetries_check(_confdata.no());
	if (!(istrst >> symmetries_check)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _symmetries; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	if (_active_symmetries != symmetries_check) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): symmetries of input differ from symmetries in dump file; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "symmetries of input coincide with _symmetries in dump file: okay" << std::endl
				<< message::unlock;
      }
      // finally, parse the partial computational result from the dump file:
      if (keyword == "_previous_triangs") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _previous_triangs)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _previous_triangs; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "_previous_triangs initialized with " << _previous_triangs << std::endl
				<< message::unlock;
      }
      if (keyword == "_new_triangs") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _new_triangs)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _new_triangs; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "_new_triangs initialized with " << _new_triangs << std::endl
				<< message::unlock;
      }
      if (keyword == "_nodecount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());      
	std::istringstream istrst (value, std::ios::in);      
	if (!(istrst >> _nodecount)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _nodecount; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "_nodecount initialized with " << _nodecount << std::endl
				<< message::unlock;
      }
      if (keyword == "_totalcount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());      
	std::istringstream istrst (value, std::ios::in);      
	if (!(istrst >> _totalcount)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _totalcount; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "_totalcount initialized with " << _totalcount << std::endl
				<< message::unlock;
      }
      if (keyword == "_symcount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _symcount)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _symcount; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "_symcount initialized with " << _symcount << std::endl
				<< message::unlock;
      }
      if (keyword == "_storedcount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _storedcount)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _storedcount; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "_storedcount initialized with " << _storedcount << std::endl
				<< message::unlock;
      }
      if (keyword == "_flipcount") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _flipcount)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _flipcount; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "_flipcount initialized with " << _flipcount << std::endl
				<< message::unlock;
      }
      if (keyword == "_flipdist") {
	lastPos = dump_line.find_first_not_of(" ", pos);
	std::string value = dump_line.substr(lastPos, dump_line.length());
	std::istringstream istrst (value, std::ios::in);
	if (!(istrst >> _flipdist)) {
	  MessageStreams::forced() << message::lock
				   << "SymmetricFlipGraph::read(std::istream& ist): error while reading _flipdist; exiting" << std::endl
				   << message::unlock;
	  exit(1);
	}
	MessageStreams::debug() << message::lock
				<< "_flipdist initialized with " << _flipdist << std::endl
				<< message::unlock;
      }
    }
    return ist;
  }

}; // namespace topcom

// eof SymmetricFlipGraph.cc
