/* this file contains the source code written for:
   Modelling brain connectomes networks: Solv is a worthy competitor to hyperbolic geometry!
   ECAI 2024 Conference Submission/
*/

#include "rogueviz.h"

#include "dhrg/dhrg.h"
#include <thread>
#include "leastsquare.cpp"
#include <sys/mman.h>
#include <fcntl.h>

namespace rogueviz {

namespace sag {

  int threads = 1;

  int informat; /* format ID */

  bool turn(int delta);
  
  int sagpar = 0;

  bool angular = false;
  bool report_tempi = false;

  int best_cost = 1000000000;

  enum eSagmode { sagOff, sagHC, sagSA };
  
  eSagmode sagmode; // 0 - off, 1 - hillclimbing, 2 - SA

  const char *sagmodes[3] = {"off", "HC", "SA"};
  
  ld temperature = -4;
  const char *loadfname;
  
  string auto_save;

  bool auto_visualize = true;

  int vizsa_start;
  int vizsa_len = 5;
  
  using subcell = pair<cell*, int>;

  /** all the SAG cells */
  vector<subcell> sagcells;

  vector<hyperpoint> sagsubcell_point;
  vector<transmatrix> sagsubcell_inv;

  struct sagdist_t {
    using distance = unsigned short;
    distance* tab;
    void* tabmap;
    int fd;
    size_t N;
    int format;

    distance* begin() { return tab; }
    distance* end() { return tab+N*N; }

    sagdist_t() { tab = nullptr; fd = 0; format = 2; }

    distance* operator [] (int y) { return tab + N * y; }

    void init(int _N, distance val) {
      clear();
      N = _N;
      tab = new distance[N*N];
      for(size_t i=0; i<N*N; i++) tab[i] = val;
      }

    void map(string fname) {
      clear();
      fd = open(fname.c_str(), O_RDONLY | O_LARGEFILE);
      if(fd == -1) throw hr_exception("open failed in map");
      read(fd, &N, 8);
      tabmap = (distance*) mmap(nullptr, N*N*sizeof(distance)+8, PROT_READ, MAP_SHARED, fd, 0);


      if(tabmap == MAP_FAILED) {
        perror("mmap");
        throw hr_exception("Mapping Failed\n");
        }

      tab = (distance*) (((char*)tabmap) + 8);
      println(hlog, "test: ", test());
      }

    void load_old(string fname) {
      vector<vector<distance>> old;
      clear();
      fhstream f(fname, "rb");
      f.read(old);
      init(isize(old), 0);
      auto ptr = tab;
      for(auto& row: old) for(auto val: row) *(ptr++) = val;
      }

    void load(string fname) {
      if(format == 1) map(fname);
      if(format == 2) load_old(fname);
      throw hr_exception("sagdist format unknown");
      }

    vector<int> test() {
      vector<int> ttab = {int(N)};
      for(int a=0; a<4; a++) for(int b=0; b<4; b++) ttab.push_back((*this)[a][b]);
      for(size_t a=N-4; a<N; a++) for(size_t b=N-4; b<N; b++) ttab.push_back((*this)[a][b]);
      return ttab;
      }

    void save(string fname) {
      fd = open(fname.c_str(), O_WRONLY | O_CREAT | O_TRUNC, 0666);
      write(fd, &N, 8);
      size_t size =  N*N*sizeof(distance);
      println(hlog, "size is ", hr::format("%zd", size));
      char *p = (char*) tab;
      while(size) {
        size_t written = write(fd, p, size);
        if(written <= 0) throw hr_exception("bad written");
        p += written; size -= written;
        }
      println(hlog, "test: ", test());
      ::close(fd);
      }

    void clear() {
      if(fd) { munmap(tabmap, N*N*sizeof(distance)+8); ::close(fd); }
      else delete[] tab;
      tab = nullptr; fd = 0;
      }

    ~sagdist_t() {
      clear();
      }

    } sagdist;

  /** what node is on sagcells[i] */
  vector<int> sagnode;

  /** node i is on sagcells[sagid[i]] */
  vector<int> sagid;

  /** sagcells[ids[c]]] == c */
  map<subcell, int> ids;

  /** if i in neighbors[j], sagcells[i] is a neighbor of sagcells[j] */
  vector<vector<int>> neighbors;
  
  ld pdist(hyperpoint hi, hyperpoint hj);
  
  /** matrix for every sagcell, not subdivided */
  vector<transmatrix> cell_matrix;

  /** point for every sagcell */
  vector<hyperpoint> cellpoint;
  
  /** precision of geometric distances */
  int gdist_prec;

  /** max edge for dijkstra */
  int dijkstra_maxedge;

  /** dijkstra with tile distances */
  bool dijkstra_tile;
  
  /** the maximum value in sagdist +1 */
  int max_sag_dist;

  vector<edgeinfo> sagedges;  
  vector<vector<int>> edges_yes, edges_no;

  enum eSagMethod { smClosest, smLogistic, smMatch };
  eSagMethod method;

  bool loglik_repeat;

  /* parameters for smMatch */
  ld match_a = 1, match_b = 0;

  /* parameters for smLogistic */
  dhrg::logistic lgsag(1, 1), lgsag_pre(1, 1);
  
  vector<ld> loglik_tab_y, loglik_tab_n;

  dhrg::logistic best;
  ld bestcost;
  int recover_from;

  int ipturn = 100;
  long long numiter = 0;
  
  int hightemp = 10;
  int lowtemp = -15;
  
  /* for the embedding method: */  
  bool embedding;
  dhrg::logistic lgemb(1, 1);
  vector<hyperpoint> placement;

  string distance_file;

  vector<hyperpoint> subcell_points;

  /** new style cell request */
  int cell_request;

  /** currently implemented only for Solv and Nil! */
  void generate_subcellpoints() {
    start_game();
    subcell_points.clear();
    println(hlog, currentmap->get_cellshape(cwt.at).vertices_only);
    ld mx = 1, my = 1, mz = 1;
    if(sol) mx = my = mz = log(2);
    for(int x=0; x<4; x++)
    for(int y=0; y<4; y++) if((x&1) == (y&1))
    for(int z=0; z<4; z++) if((x&1) == (z&1)) {
      subcell_points.push_back(point31(mx * (x+.5-2)/4, my * (y+.5-2)/4, mz * (z+.5-2)/4));
      }
    println(hlog, subcell_points);
    }

  bool viz_subcellpoints(cell *c, const shiftmatrix& V) {
    for(auto p: subcell_points) queuepoly(V * rgpushxto0(p), cgi.shSnowball, 0xFFFFFFFF);
    return false;
    }

  bool viz_sagcr(cell *c, const shiftmatrix& V) {
    for(int i=0; i<isize(subcell_points); i++) {
      auto p = at_or_null(ids, pair<cell*,int>{c, i});
      if(!p) continue;
      queuepolyat(V * rgpushxto0(subcell_points[i]), cgi.shSnowball, 0x80FF80FF, PPR::FLOORb);

      for(auto nei: neighbors[*p]) if(nei<*p) {
        queueline(V * subcell_points[i], V * sagsubcell_inv[*p] * sagsubcell_point[nei], 0x8000FF, 3).prio = PPR::FLOORa;
        }
      }
    return false;
    }

  void compute_dists() {
    int N = isize(sagcells);

    neighbors.clear();
    neighbors.resize(N);

    int Q = isize(subcell_points);

    for(int b=0; b<Q; b++)
    for(int i=0; i<N; i++)
      for(cell *c1: adj_minefield_cells(sagcells[i].first))
        if(ids.count({c1,b})) neighbors[i].push_back(ids[{c1,b}]);

    for(int i=0; i<N; i++) for(int b=0; b<Q; b++) if(b != sagcells[i].second)
      neighbors[i].push_back(ids[{sagcells[i].first, b}]);

    const ld ERRORV = -17.3;
    transmatrix unknown = Id; unknown[0][0] = ERRORV;
    cell_matrix.clear();
    cell_matrix.resize(N, unknown);
    cellpoint.clear();
    cellpoint.resize(N, C0);
    vector<int> visited;

    auto visit = [&] (int id, const transmatrix& T) {
      if(cell_matrix[id][0][0] != ERRORV) return;
      cell_matrix[id] = T;
      visited.push_back(id);
      };

    visit(0, Id);
    for(int i=0; i<isize(visited); i++) {
      cell *c0 = sagcells[visited[i]].first;
      const transmatrix& T0 = cell_matrix[visited[i]];
      for(int d=0; d<c0->type; d++)
        if(ids.count({c0->move(d), 0}))
          visit(ids[{c0->move(d), 0}], T0 * currentmap->adj(c0, d));
      for(int q=0; q<Q; q++)
        cellpoint[visited[i]/Q*Q+q] = T0 * subcell_points[q];
      }
    
    println(hlog, "A");
    if(distance_file != "") {
      println(hlog, "B");
      sagdist.map(distance_file);
      }
    else if(gdist_prec && dijkstra_maxedge) {
      sagdist.init(N, N);
      println(hlog, "Computing Dijkstra distances...");
      vector<vector<pair<int, ld>>> dijkstra_edges(N);
      for(int i=0; i<N; i++) {
        celllister cl(sagcells[i].first, dijkstra_maxedge, 50000, nullptr);
        for(auto c1: cl.lst) for(int q=0; q<Q; q++) if(c1 != sagcells[i].first || q != sagcells[i].second) if(ids.count({c1, q}))
          dijkstra_edges[i].emplace_back(ids[{c1, q}], pdist(cellpoint[i], cellpoint[ids[{c1, q}]]));
        if(i == 0) println(hlog, i, " has ", isize(dijkstra_edges[i]), " edges");
        }
      parallelize(N, [&] (int a, int b) {
      vector<ld> distances(N);
      for(int i=a; i<b; i++) {
        if(i % 500 == 0) println(hlog, "computing dijkstra for ", i , "/", N);
        for(int j=0; j<N; j++) distances[j] = HUGE_VAL;
        std::priority_queue<pair<ld, int>> pq;
        auto visit = [&] (int i, ld dist) {
          if(distances[i] <= dist) return;
          distances[i] = dist;
          pq.emplace(-dist, i);
          };
        visit(i, 0);
        while(!pq.empty()) {
          ld d = -pq.top().first;
          int at = pq.top().second;
          pq.pop();
          for(auto e: dijkstra_edges[at]) visit(e.first, d + e.second);
          }
        for(int j=0; j<N; j++) sagdist[i][j] = distances[j] * gdist_prec + .5;
        }
      return 0;
      }
      );
      println(hlog, "N0 = ", neighbors[0]);
      println(hlog, "N1 = ", neighbors[1]);
      }

    else if(gdist_prec) {
      sagdist.init(N, N);
      println(hlog, "Computing distances... (N=", N, ")");
      ld mx = 1;
      for(int i=0; i<N; i++)
      for(int j=0; j<N; j++) {
        ld d = pdist(cellpoint[i], cellpoint[j]);
        sagdist[i][j] = (d + .5) * gdist_prec;
        if(d > mx) {
          println(hlog, kz(cellpoint[i]), kz(cellpoint[j]), " :: ", mx = d);
          }
        }
      }
    
    else {
      println(hlog, "no gdist_prec");
      sagdist.init(N, N);
      for(int i=0; i<N; i++) {
        auto sdi = sagdist[i];
        vector<int> q;
        auto visit = [&] (int j, int dist) { if(sdi[j] < N) return; sdi[j] = dist; q.push_back(j); };
        visit(i, 0);
        for(int j=0; j<isize(q); j++) for(int k: neighbors[q[j]]) visit(k, sdi[q[j]]+1);
        }
      }
    
    max_sag_dist = 0;
    for(auto x: sagdist) max_sag_dist = max<int>(max_sag_dist, x);
    max_sag_dist++;
    println(hlog, "max_sag_dist = ", max_sag_dist);
    }

  bool legacy;

  /* legacy method */
  void init_snake(int n) {
    sagcells.clear();
    ids.clear();

    auto enlist = [&] (cellwalker cw) {
      ids[{cw.at, 0}] = isize(sagcells);
      sagcells.emplace_back(cw.at, 0);
      };

    cellwalker cw = cwt;
    enlist(cw);
    cw += wstep;
    enlist(cw);
    for(int i=2; i<n; i++) {
      cw += wstep;
      while(ids.count({cw.at, 0})) {
        cw = cw + wstep + 1 + wstep;
        }
      enlist(cw); cw += 1;
      }
    }

  void init_sag_cells() {

    if(isize(subcell_points) <= 1) subcell_points = { C0 };

    sagcells.clear();
    for(auto c: currentmap->allcells()) for(int i=0; i<isize(subcell_points); i++) {
      ids[{c, i}] = isize(sagcells);
      sagcells.emplace_back(c, i);
      }
    }

  void compute_creq_neighbors() {
    int SN = isize(sagcells);
    neighbors.resize(SN);
    vector<int> mindist_for(SN, 30000);
    for(int i=0; i<SN; i++) {
      auto& m = mindist_for[i];
      for(int j=0; j<SN; j++) if(j != i) m = min<int>(m, sagdist[i][j]);
      }

    for(int i=0; i<SN; i++)
    for(int j=0; j<SN; j++) if(i != j && sagdist[i][j] < mindist_for[i] + mindist_for[j]) neighbors[i].push_back(j);

    max_sag_dist = 0;
    for(auto x: sagdist) max_sag_dist = max<int>(max_sag_dist, x);
    max_sag_dist++;
    println(hlog, neighbors[0]);
    hlog.flush();
    }

  vector<vector<pair<ld, subcell>>> dijkstra_edges;

  void find_cells() {
    println(hlog, "cellcount = ", cellcount);
    if(isize(subcell_points) <= 1) subcell_points = { C0 };
    struct qitem {
      ld dist; subcell sc; transmatrix T; 
      bool operator < (const qitem& b) const { return dist > b.dist + 1e-6; }
      };     

    std::priority_queue<qitem> pq;
    auto visit = [&] (subcell sc, ld dist, const transmatrix& T) {
      if(ids.count(sc)) return;
      pq.emplace(qitem{dist, sc, T});
      };

    sagsubcell_point.clear();
    sagsubcell_inv.clear();

    int Q = isize(subcell_points);
    visit(subcell{cwt.at,0}, 0, Id);
    ld maxdist0 = 0;
    for(int i=0;; i++) {
      if(pq.empty()) { println(hlog, "no more"); break; }
      auto p = pq.top();
      pq.pop();
      ld dist = p.dist;
      auto sc = p.sc;
      transmatrix T = p.T;
      if(ids.count(sc)) { i--; continue; }
      if(i == cell_request-1) maxdist0 = dist;
      if(i >= cell_request && dist > maxdist0 + 1e-6) break;

      sagcells.push_back(sc);
      sagsubcell_point.push_back(T * subcell_points[sc.second]);
      sagsubcell_inv.push_back(inverse(T));
      ids[sc] = i;
      // println(hlog, "cell ", i, " is ", sc, " at ", sagsubcell_point.back(), " in distance ", dist);

      if(dijkstra_maxedge) {
        dijkstra_edges.emplace_back();
        auto& de = dijkstra_edges.back();

        set<cell*> vis;
        vector<tuple<cell*, transmatrix, int>> q;
        auto visit1 = [&] (cell *c, transmatrix T, int d) {
          if(vis.count(c)) return;
          vis.insert(c);
          q.emplace_back(c, T, d);
          };
        visit1(sc.first, Id, 0);
        for(int i1=0; i1 < isize(q); i1++)  {
          cell *c = get<0>(q[i1]);
          transmatrix T1 = get<1>(q[i1]);
          int dist1 = get<2>(q[i1]);
          if(dist1 < dijkstra_maxedge) for(int j=0; j<c->type; j++) {
            cell *c1 = c->cmove(j);
            visit1(c1, T1 * currentmap->adj(c, j), dist1+1);
            }

          for(int q=0; q<Q; q++) {
            subcell sc1 {c, q};
            ld ndist = dijkstra_tile ? dist1 : pdist(subcell_points[sc.second], T1 * subcell_points[q]);
            de.push_back({ndist, sc1});
            visit(sc1, dist + ndist, T*T1);
            }
          }
        }
      else {
        for(int j=0; j<sc.first->type; j++) for(int k=0; k<Q; k++) {
          cell *c1 = sc.first->cmove(j);
          transmatrix T1 = T * currentmap->adj(sc.first, j);
          visit(subcell{c1, k}, pdist(C0, T1*C0), T1);
          }
        }
      }
    int SN = isize(sagcells);
    println(hlog, "number of cells found: ", SN, " dijkstra_maxedge = ", dijkstra_maxedge);

    all_disk_cells_sorted = {};
    for(auto p: sagcells) if(all_disk_cells_sorted.empty() || p.first != all_disk_cells_sorted.back()) all_disk_cells_sorted.push_back(p.first);

    for(cell *c: all_disk_cells_sorted) setdist(c, 7, c);
    set<cell*> all_disk_cells_set;
    for(cell *c: all_disk_cells_sorted) all_disk_cells_set.insert(c);

    for(cell *c: all_disk_cells_sorted) forCellEx(c1, c) if(!all_disk_cells_set.count(c1)) {
      c1->landparam = 0xE08080, c1->wall = waNone, c1->land = laCanvas;
      }
    }

  void init_cell_request() {
    println(hlog, "generating on cell request");
    find_cells();

    int SN = isize(sagcells);
    sagdist.init(SN, 0);

    if(!dijkstra_maxedge) {
      println(hlog, "computing sagdist ...");
      parallelize(SN, [&] (int a, int b) {
        for(int i=a; i<b; i++) {
          for(int j=0; j<SN; j++) {
            ld dist = pdist(sagsubcell_point[i], sagsubcell_point[j]);
            sagdist[i][j] = int(dist * gdist_prec + 0.5);
            if(i < j && sagdist[i][j] == 0) println(hlog, "for ", tie(i,j), " pdist computed as ", dist);
            }
          }
        return 0;
        });
      println(hlog, "... done");
      }
   else {
      vector<vector<pair<ld, int>>> dijkstra_edges_2;
      dijkstra_edges_2.resize(SN);
      for(int i=0; i<SN; i++) for(auto p: dijkstra_edges[i]) if(ids.count(p.second)) dijkstra_edges_2[i].emplace_back(p.first, ids[p.second]);

      parallelize(SN, [&] (int a, int b) {
        vector<ld> distances(SN);
        for(int i=a; i<b; i++) {
          if(i % 500 == 0) println(hlog, "computing dijkstra for ", i , "/", SN);
          for(int j=0; j<SN; j++) distances[j] = HUGE_VAL;
          std::priority_queue<pair<ld, int>> pq;
          auto visit = [&] (int i, ld dist) {
            if(distances[i] <= dist) return;
            distances[i] = dist;
            pq.emplace(-dist, i);
            };
          visit(i, 0);
          while(!pq.empty()) {
            ld d = -pq.top().first;
            int at = pq.top().second;
            pq.pop();
            for(auto e: dijkstra_edges_2[at]) {
              // println(hlog, "move from ", at, " to ", e.first, " for ", d, "+", e.second);
              visit(e.second, d + e.first);
              }
            }
          for(int j=0; j<SN; j++) sagdist[i][j] = distances[j] * gdist_prec + .5;
          }
        return 0;
        });
      }

    compute_creq_neighbors();
    }

  void init_sag_cells_gen() {
    if(sagcells.size()) { println(hlog, "sagcells already known"); }
    else if(cell_request) {
      if(distance_file != "") {
        println(hlog, "loading graph ", distance_file);
        sagdist.map(distance_file);
        if(noGUI) {
          sagcells.resize(sagdist.N, subcell{nullptr, 0});
          }
        else {
          find_cells();
          }
        println(hlog, "computing neighbors");
        compute_creq_neighbors();
        println(hlog, "neighbors computed");
        }
      else
        init_cell_request();
      }
    else if(legacy)
      init_snake(2 * isize(sagid));
    else
      init_sag_cells();
    if(!cell_request) compute_dists();
    }

  /* separate hubs -- only for smClosest */
  ld hub_penalty;
  string hub_filename;
  vector<int> hubval;
  
  double costat(int vid, int sid) {
    if(vid < 0) return 0;
    double cost = 0;
    
    if(method == smLogistic) {
      auto s = sagdist[sid];
      for(auto j: edges_yes[vid]) if(sagid[j] >= -1)
        cost += loglik_tab_y[s[sagid[j]]];
      for(auto j: edges_no[vid]) if(sagid[j] >= -1)
        cost += loglik_tab_n[s[sagid[j]]];
      return -cost;
      }
    
    if(method == smMatch) {
      vertexdata& vd = vdata[vid];
      for(int j=0; j<isize(vd.edges); j++) {
        edgeinfo *ei = vd.edges[j].second;
        int t2 = vd.edges[j].first;
        if(sagid[t2] != -1) {
          ld cdist = sagdist[sid][sagid[t2]];
          ld expect = match_a / ei->weight2 + match_b;
          ld dist = cdist - expect;
          cost += dist * dist;
          }
        }
      return cost;
      }

    vertexdata& vd = vdata[vid];
    for(int j=0; j<isize(vd.edges); j++) {
      edgeinfo *ei = vd.edges[j].second;
      int t2 = vd.edges[j].first;
      if(sagid[t2] != -1) cost += sagdist[sid][sagid[t2]] * ei->weight2;
      }
    
    if(!hubval.empty()) {
      for(auto sid2: neighbors[sid]) {
        int vid2 = sagnode[sid2];
        if(vid2 >= 0 && (hubval[vid] & hubval[vid]) == 0)
          cost += hub_penalty;
        }
      }
    
    return cost;
    }
  
  // std::mt19937 los;

  double cost;

  vector<double> chgs;  
  
  edgetype *sag_edge;

  void forgetedges(int id) {
    for(int i=0; i<isize(vdata[id].edges); i++) 
      vdata[id].edges[i].second->orig = NULL;
    }
  
  bool chance(double p) {
    p *= double(hrngen.max()) + 1;
    auto l = hrngen();
    auto pv = (decltype(l)) p;
    if(l < pv) return true;
    if(l == pv) return chance(p-pv);
    return false;
    }

  void compute_cost();
  bool opt_debug = false;
  bool should_good = false;

  bool allow_doubles = false;

  bool twoway = false;

  int moves, nomoves;

  void saiter() {
    int DN = isize(sagid);
    int t1 = hrand(DN);
    int sid1 = sagid[t1];
    
    int sid2;
    
    int s = twoway ? pick(1,4) : hrand(4)+1;

    if(s == 4) sid2 = hrand(isize(sagcells));
    else {
      sid2 = sid1;
      for(int ii=0; ii<s; ii++) sid2 = hrand_elt(neighbors[sid2]);
      }
    int t2 = allow_doubles ? -1 : sagnode[sid2];
    
    sagnode[sid1] = -1; sagid[t1] = -1;
    sagnode[sid2] = -1; if(t2 >= 0) sagid[t2] = -1;
    
    double change = 
      costat(t1,sid2) + costat(t2,sid1) - costat(t1,sid1) - costat(t2,sid2);
    
    sagnode[sid1] = t1; sagid[t1] = sid1;
    sagnode[sid2] = t2; if(t2 >= 0) sagid[t2] = sid2;
    
    if(change > 0 && (sagmode == sagHC || !chance(exp(-change * exp(-temperature))))) { nomoves++; return; }
    moves++;

    sagnode[sid1] = t2; sagnode[sid2] = t1;
    sagid[t1] = sid2; if(t2 >= 0) sagid[t2] = sid1;

    if(should_good) {
      auto dcost = cost;
      compute_cost();
      println(hlog, "dcost=", dcost, " change=", change, " cost=", cost, " error = ", dcost + change - cost);
      if(abs(dcost + change - cost) > .1) throw hr_exception("dcost fail");
      cost = dcost;
      }

    cost += change;

    }

  ld checkmark_cost;

  void compute_cost() {
    int DN = isize(sagid);
    cost = 0;
    for(int i=0; i<DN; i++)
      cost += costat(i, sagid[i]);
    cost /= 2;
    }
  
  int hillclimb() {
    int DN = isize(sagid);
    int changes = 0;
    vector<ld> succ;

    for(int t1=0; t1<DN; t1++) {
      int sid1 = sagid[t1];    
      for(int sid2: neighbors[sid1]) {
        int t2 = allow_doubles ? -1 : sagnode[sid2];

        sagnode[sid1] = -1; sagid[t1] = -1;
        sagnode[sid2] = -1; if(t2 >= 0) sagid[t2] = -1;
        
        double change = 
          costat(t1,sid2) + costat(t2,sid1) - (costat(t1,sid1) + costat(t2,sid2));

        if(change >= -1e-10) {
          sagnode[sid1] = t1; sagid[t1] = sid1;
          sagnode[sid2] = t2; if(t2 >= 0) sagid[t2] = sid2;
          }
        else {
          changes++;
          sagnode[sid1] = t2; sagnode[sid2] = t1;
          sagid[t1] = sid2; if(t2 >= 0) sagid[t2] = sid1;
          cost += change;
          succ.push_back(change);
          break;
          }
        }
      }
    // println(hlog, "successes = ", succ);

    return changes;
    }

  int checkmark_hillclimb() {
    compute_cost();
    if(cost > checkmark_cost) {
      println(hlog, "checkmark failed");
      throw hr_exception("checkmark failed");
      return 0;
      }
    checkmark_cost = cost;
    return hillclimb();
    }

  void prepare_graph() {
    int DN = isize(sagid);
    println(hlog, "prepare_graph with DN = ", DN);

    set<pair<int, int>> alledges;
    for(auto e: sagedges) {
      if(e.i == e.j) continue;
      alledges.emplace(e.i, e.j);
      alledges.emplace(e.j, e.i);
      }
    
    edges_yes.clear(); edges_yes.resize(DN);
    edges_no.clear(); edges_no.resize(DN);
    
    for(int i=0; i<DN; i++) for(int j=0; j<DN; j++) if(i != j) {
      if(alledges.count({i, j}))
        edges_yes[i].push_back(j);
      else
        edges_no[i].push_back(j);
      }          

    sagnode.clear();
    sagnode.resize(isize(sagcells), -1);
    for(int i=0; i<DN; i++)
      sagnode[sagid[i]] = i;
    compute_cost();
    }

  void set_inverse() {
    if(method == smMatch) vizflags |= RV_INVERSE_WEIGHT;
    else vizflags &=~ RV_INVERSE_WEIGHT;
    }

  void reassign() {
    if(sagcells[0].first == nullptr) return;
    int DN = isize(sagid);

    vector<int> qon(isize(sagcells), 0);
    for(int i=0; i<DN; i++) qon[sagid[i]]++;
    vector<int> qsf(isize(sagcells), 0);

    ld rad = .25 * cgi.scalefactor;
    if(isize(subcell_points) > 1) rad /= pow(isize(subcell_points), WDIM);

    for(int i=0; i<DN; i++) {
      int ci = sag::sagid[i];
      vdata[i].m->base = sagcells[ci].first;
      vdata[i].m->at = Id;

      if(allow_doubles) vdata[i].m->at = 
        spin(TAU*(qsf[ci]++) / qon[ci]) * xpush(rad * (qon[ci]-1) / qon[ci]);

      if(isize(subcell_points) > 1)
        vdata[i].m->at = rgpushxto0(subcell_points[sagcells[ci].second]) * vdata[i].m->at;

      forgetedges(i);
      }
    shmup::fixStorage();
    set_inverse();
    }

  void load_sag_solution(const string& fname) {
    printf("Loading the sag from: %s\n", fname.c_str());
    FILE *sf = fopen(fname.c_str(), "rt");
    if(!sf) { printf("Failed to open file.\n"); exit(1); }
    int SN = isize(sagcells);
    if(sf) while(true) {
      string lab;
      while(true) {
        int c = fgetc(sf);
        if(c == EOF) goto afterload;
        else if(c == ',' || c == ';') break;
        else if(rv_ignore(c)) ;
        else lab += c;
        }
      int sid = -1;
      int err = fscanf(sf, "%d", &sid);
      if(sid < 0 || sid >= SN || err < 1) sid = -1;
      if(!labeler.count(lab)) {
        printf("unknown vertex: %s\n", lab.c_str());
        }
      else {
        int id = getid(lab);
        sagid[id] = sid;
        }
      }
    afterload: 
    if(sf) fclose(sf);

    prepare_graph();
    reassign();
    }

  int view_each = 1000;

  void dofullsa(ld satime) {
    sagmode = sagSA;
    int t1 = SDL_GetTicks();
    int tl = -999999;
    
    while(true) {
      int t2 = SDL_GetTicks();
      double d = (t2-t1) / (1000. * satime);
      if(d > 1) break;

      temperature = hightemp - (d*(hightemp-lowtemp));
      for(int i=0; i<10000; i++) {
        numiter++;
        sag::saiter();
        }
      
      if(t2 - tl > view_each * .98) {
        tl = t2;
        println(hlog, format("it %12Ld temp %6.4f [1/e at %13.6f] cost = %f ",
          numiter, double(sag::temperature), (double) exp(sag::temperature),
          double(sag::cost)));
        }
      
      }
    
    temperature = -5;
    sagmode = sagOff;
    reassign();
    }

  int recost_each;
  int autofix_rt;

  void optimize_sag_loglik_logistic();
  void compute_loglik_tab();

  bool output_fullsa = true;

  void dofullsa_iterations(long long saiter) {
    sagmode = sagSA;
    moves = 0; nomoves = 0; numiter = 0;

    // decltype(SDL_GetTicks()) t1 = SDL_GetTicks();

    // println(hlog, "before dofullsa_iterations, cost = ", double(sag::cost), " iterations = ", fts(saiter));

    ld last_ratio;

    int lpct = 0;

    bool was_fixed = false;

    for(int i=0; i<saiter; i++) {

      temperature = hightemp - ((i+.5)/saiter*(hightemp-lowtemp));
      numiter++;
      sag::saiter();

      if(recost_each && moves > recost_each) {
        last_ratio = moves / (moves + nomoves + 0.);
        if(((autofix_rt == 3 && was_fixed) || nomoves > recost_each) && autofix_rt) {
          was_fixed = true;
          optimize_sag_loglik_logistic();
          if(autofix_rt == 1) {
            lgsag.T = best.T; 
            compute_loglik_tab();
            compute_cost();
            }
          }
        nomoves = 0; moves = 0;
        }

      int cpct = numiter * 20 / (saiter-1);

      if(cpct > lpct && output_fullsa) {
        lpct = cpct;
        println(hlog, format("it %12Ld ratio %6.3f temp %8.4f step %9.3g cost %9.2f R=%8.4f T=%8.4f",
          numiter, last_ratio, double(sag::temperature), (double) exp(sag::temperature), cost, lgsag.R, lgsag.T));
        }

      /* if(numiter % 10000 == 0) {
        auto t2 = SDL_GetTicks();
        if(int(t2 - t1) > view_each) {
          t1 = t2;
          println(hlog, format("it %12Ld temp %6.4f [1/e at %13.6f] cost = %f ",
            numiter, double(sag::temperature), (double) exp(sag::temperature),
            double(sag::cost)));
          }
        } */
      }

    // println(hlog, "after dofullsa_iterations, cost = ", double(sag::cost));

    temperature = -5;
    sagmode = sagOff;
    reassign();
    }

  int sag_ittime = 100;

  void iterate() {
    if(!sagmode) return;
    int t1 = SDL_GetTicks();
    #if CAP_SDL && !CAP_SDL2
    int last = -1;
    #endif
    for(int i=0; i<ipturn; i++) {
      numiter++;
      sag::saiter();
      #if CAP_SDL && !CAP_SDL2
      int q = i * sag_ittime / ipturn;
      if(q > last) { last = 1; SDL_PumpEvents(); }
      #endif
      }
    int t2 = SDL_GetTicks();
    int t = t2 - t1;
    if(t < (sag_ittime+1) / 2) ipturn *= 2;
    else if(t > sag_ittime * 2) ipturn /= 2;
    else ipturn = ipturn * sag_ittime / t;
    print(hlog, format("it %12Ld temp %6.4f [2:%8.6f,10:%8.6f,50:%8.6f] cost = %f\n",
      numiter, double(sag::temperature), 
      (double) exp(-2 * exp(-sag::temperature)),
      (double) exp(-10 * exp(-sag::temperature)),
      (double) exp(-50 * exp(-sag::temperature)),
      (double) sag::cost));

    if(auto_visualize) reassign();
    }
  
  void save_sag_solution(const string& fname) {
    FILE *f = fopen(fname.c_str(), "wt");
    for(int i=0; i<isize(sagid); i++)
      fprintf(f, "%s;%d\n", vdata[i].name.c_str(), sagid[i]);
    fclose(f);
    }

  void compute_loglik_tab() {
    loglik_tab_y.resize(max_sag_dist);
    loglik_tab_n.resize(max_sag_dist);
    for(int i=0; i<max_sag_dist; i++) {
      loglik_tab_y[i] = lgsag.lyes(i);
      loglik_tab_n[i] = lgsag.lno(i);
      }
    }

  void compute_auto_rt() {
    ld sum0 = 0, sum1 = 0, sum2 = 0;

    for(auto i: sagdist) {
      sum0 ++;
      sum1 += i;
      sum2 += i*i;
      }

    lgsag.R = sum1 / sum0;
    lgsag.T = sqrt((sum2 - sum1*sum1/sum0) / sum0);
    println(hlog, "automatically set R = ", lgsag.R, " and ", lgsag.T, " max_sag_dist = ", max_sag_dist);
    if(method == smLogistic) compute_loglik_tab();
    }

  void optimize_sag_loglik_logistic() {
    vector<int> indist(max_sag_dist, 0);
    
    const int mul = 1;

    int N = isize(sagid);
    for(int i=0; i<N; i++)
    for(int j=0; j<i; j++) {
      int d = sagdist[sagid[i]][sagid[j]];
      indist[d]++;
      }
    
    vector<int> pedge(max_sag_dist, 0);
      
    for(int i=0; i<isize(sagedges); i++) {
      edgeinfo& ei = sagedges[i];
      // if(int(sagdist[sagid[ei.i]][sagid[ei.j]] * mul) == 136) printf("E %d,%d\n", ei.i, ei.j);
      if(ei.i != ei.j)
      if(ei.weight >= sag_edge->visible_from)
        pedge[sagdist[sagid[ei.i]][sagid[ei.j]] * mul]++;
      }
    
    if(opt_debug) for(int d=0; d<max_sag_dist; d++) 
      if(indist[d])
        printf("%2d: %7d/%7d %7.3lf\n", 
          d, pedge[d], indist[d], double(pedge[d] * 100. / indist[d]));
        
    ld loglik = 0;
    for(int d=0; d<max_sag_dist; d++) {
      int p = pedge[d], pq = indist[d];
      int q = pq - p;
      if(p && q) {
        loglik += p * log(p) + q * log(q) - pq * log(pq);
        if(opt_debug) println(hlog, tie(d, p, q), loglik);
        }
      }
    
    if(opt_debug) println(hlog, "loglikelihood best = ", fts(loglik));
    
    auto logisticf = [&] (dhrg::logistic&  l) {
      ld loglik = 0;
      for(int d=0; d<max_sag_dist; d++) {
        int p = pedge[d], pq = indist[d];
        if(p) loglik += p * l.lyes(d);
        if(pq > p) loglik += (pq-p) * l.lno(d);
        }
      return loglik;
      };

    if(opt_debug) println(hlog, "cost = ", cost, " logisticf = ", logisticf(lgsag), " R= ", lgsag.R, " T= ", lgsag.T);
    if(should_good && abs(cost + logisticf(lgsag)) > 0.1) throw hr_exception("computation error");
    
    dhrg::fast_loglik_cont(lgsag, logisticf, nullptr, 1, 1e-5);
    if(opt_debug) println(hlog, "loglikelihood logistic = ", logisticf(lgsag), " R= ", lgsag.R, " T= ", lgsag.T);    
    
    if(method == smLogistic) {
      compute_loglik_tab();
      compute_cost();
      if(opt_debug) println(hlog, "cost = ", cost);
      }
    }

  void optimize_sag_loglik_match() {
    lsq::leastsquare_solver<2> lsqs;

    for(auto& ei: sagedges) {
      ld y = sagdist[sagid[ei.i]][sagid[ei.j]];
      ld x = 1. / ei.weight;
      lsqs.add_data({{x, 1}}, y);
      }

    array<ld, 2> solution = lsqs.solve();
    match_a = solution[0];
    match_b = solution[1];

    println(hlog, "got a = ", match_a, " b = ", match_b);
    if(method == smMatch)
      prepare_graph();
    }

  void optimize_sag_loglik_auto() {
    if(method == smLogistic) optimize_sag_loglik_logistic();
    if(method == smMatch) optimize_sag_loglik_match();
    }

  void disttable_add(ld dist, int qty0, int qty1) {
    using namespace dhrg;
    size_t i = dist * llcont_approx_prec;
    constexpr array<ll, 2> zero = {0, 0};
    while(disttable_approx.size() <= i) disttable_approx.push_back(zero);
    disttable_approx[i][0] += qty0;
    disttable_approx[i][1] += qty1;
    }
  
  ld approx_01(hyperpoint h) {
    ld d = 0;
    if(h[0] > 1) {
      ld z = log(h[0]);
      d += z; h[1] *= h[0]; h[0] = 1; h[2] += z;
      }
    d += h[0];
    if(h[1] > 1) {
      ld z = log(h[1]);
      d += z; h[1] = 1; h[2] -= z;
      }
    d += h[1];
    d += abs(h[2]);
    return d;
    }

  ld pdist(hyperpoint hi, hyperpoint hj) {
    if(sol && angular) {
      return 10 * asinh(hypot_d(3, lie_log(shiftless(gpushxto0(hi) * hj))) / 10);
      }
    if(sol) return min(geo_dist(hi, hj), geo_dist(hj, hi));
    if(mproduct && angular) {

      auto di = product_decompose(hi);
      auto dj = product_decompose(hj);
      ld x = hdist(di.second, dj.second);
      ld z = di.first - dj.first;
      auto res = sqrt((x*x+z*z) * (x > 0 ? sinh(x) / x : 1));
      return res;
      }
    return geo_dist(hi, hj);
    };

  ld pdist(int i, int j) {
    return pdist(placement[i], placement[j]);
    };

  void prepare_embedding() {
    map<int, transmatrix> maps;    
    vector<int> visited;

    auto visit = [&] (int id, const transmatrix& T) {
      if(maps.count(id)) return;
      maps[id] = T;
      visited.push_back(id);
      };
      
    visit(0, Id);
    for(int i=0; i<isize(visited); i++) {
      cell *c0 = sagcells[i].first;
      transmatrix T0 = maps[i];
      for(int d=0; d<c0->type; d++)
        if(ids.count({c0->move(d), 0}))
          visit(ids[{c0->move(d), 0}], T0 * currentmap->adj(c0, d));
      }
    
    int DN = isize(sagid);
    placement.resize(DN);
    for(int i=0; i<DN; i++) placement[i] = tC0(maps[sagid[i]]);    
    }
  
  int embiter;
  
  void compute_loglik() {    
    dhrg::llcont_approx_prec = 10;
    
    dhrg::disttable_approx.clear();
    int DN = isize(sagid);
    for(int i=0; i<DN; i++)
    for(int j=0; j<i; j++)
      disttable_add(pdist(i, j), 1, 0);

    for(int i=0; i<isize(sagedges); i++) {
      edgeinfo& ei = sagedges[i];
      if(ei.i != ei.j)
        disttable_add(pdist(ei.i, ei.j), -1, 1);
      }
    
    dhrg::logisticfun lc = dhrg::loglik_cont_approx;

    dhrg::fast_loglik_cont(lgemb, lc, nullptr, 1, 1e-5);
    
    println(hlog, "loglik = ", format("%.6f", lc(lgemb)), " R = ", lgemb.R, " T = ", lgemb.T, " iterations = ", embiter);
    }

  void reassign_embedding() {
    int DN = isize(sagid);
    for(int i=0; i<DN; i++) {
      vdata[i].m->base = sagcells[0].first;
      vdata[i].m->at = rgpushxto0(placement[i]);
      virtualRebase(vdata[i].m);
      forgetedges(i);
      }
    shmup::fixStorage();
    }

  void improve_embedding() {
    embiter++;
    if(placement.empty()) {
      prepare_embedding();
      compute_loglik();
      }
    ld eps = .1;
    int DN = isize(sagid);

    hyperpoint h = C0;
    for(int i=0; i<WDIM; i++) h[i] += (hrandf() - 0.5) * eps;
    h = normalize(h);
    
    auto nplacement = placement;
    parallelize(DN, [&] (int a, int b) {
      for(int i=a; i<b; i++) {

        hyperpoint np = rgpushxto0(placement[i]) * h;
        
        ld change;
        for(auto e: edges_yes[i]) change -= lgemb.lyes(pdist(placement[i], placement[e]));
        for(auto e: edges_no[i]) change -= lgemb.lno(pdist(placement[i], placement[e]));
        for(auto e: edges_yes[i]) change += lgemb.lyes(pdist(np, placement[e]));
        for(auto e: edges_no[i]) change += lgemb.lno(pdist(np, placement[e]));
  
        if(change > 0) nplacement[i] = np;
        }
      return 0;
      });
    
    placement = nplacement;
    }
  
  int embturn = 1;
  void embedding_iterate() {
    int t1 = SDL_GetTicks();
    for(int i=0; i<embturn; i++) {
      improve_embedding();
      }
    int t2 = SDL_GetTicks();
    int t = t2 - t1;
    if(t < 50) embturn *= 2;
    else if(t > 200) embturn = (embturn + 1) / 2;
    else embturn = (embturn * 100 + (t-1)) / t;
    
    compute_loglik();
    if(auto_visualize) reassign_embedding();
    }

  void save_embedding(const string& fname) {
    fhstream f(fname, "wt");
    for(int i=0; i<isize(sagid); i++) {
      println(f, vdata[i].name);
      for(int d=0; d<MDIM; d++)
        println(f, format("%.20f", placement[i][d]));
      }
    }

  void load_embedding(const string& fname) {
    prepare_embedding();
    fhstream f(fname, "rt");
    if(informat == 2) {
      /* H2 embedding */
      while(!feof(f.f)) {
        string lab = scan<string>(f);
        int id;
        if(!labeler.count(lab)) {
          printf("unknown vertex: %s\n", lab.c_str());
          continue;
          }
        else id = getid(lab);
        ld alpha, r;
        if(1) {
          dynamicval<eGeometry> g(geometry, gNormal);
          hyperpoint h;
          for(int d=0; d<MDIM; d++) h[d] = scan<ld>(f);
          alpha = atan2(h);
          r = hdist0(h);
          println(hlog, "read ", lab, " as ", h, " which is ", tie(alpha, r));
          }
        placement[id] = direct_exp(cspin(0, 2, alpha) * ctangent(0, r));
        println(hlog, "dist = ", pdist(placement[id], C0), " expected: ", r);
        }
      }
    else if(informat == 3) {
      /* BFKL */
      string ignore;
      if(!scan(f, ignore, ignore, ignore, ignore, ignore, ignore, ignore, ignore)) {
        printf("Error: incorrect format of the first line\n"); exit(1);
        }      
      while(true) {
        string lab = scan<string>(f);
        if(lab == "" || lab == "#ROGUEVIZ_ENDOFDATA") break;
        ld r, alpha;
        if(!scan(f, r, alpha)) { printf("Error: incorrect format of r/alpha\n"); exit(1); }
        hyperpoint h = spin(alpha * degree) * xpush0(r);
  
        if(!labeler.count(lab)) {
          printf("unknown vertex: %s\n", lab.c_str());
          }
        else {
          int id = getid(lab);
          placement[id] = h;
          }      
        }
      }
    else if(informat == 4) {
      while(true) {
        string lab = scan<string>(f);
        if(lab == "") break;
        ld r, alpha;
        if(!scan(f, r, alpha)) { printf("Error: incorrect format of r/alpha\n"); exit(1); }
        hyperpoint h = spin(alpha) * xpush0(r);
  
        if(!labeler.count(lab)) {
          printf("unknown vertex: %s\n", lab.c_str());
          }
        else {
          int id = getid(lab);
          placement[id] = h;
          }      
        }
      }
    else {
      while(!feof(f.f)) {
        string lab = scan<string>(f);
        int id;
        if(!labeler.count(lab)) {
          printf("unknown vertex: %s\n", lab.c_str());
          continue;
          }
        else id = getid(lab);
        hyperpoint h;
        for(int d=0; d<MDIM; d++) h[d] = scan<ld>(f);
        placement[id] = h;
        }
      }
    reassign_embedding();
    compute_loglik();
    }
  
  void read_hubs(const string& fname) {
    hubval.resize(isize(vdata), -1);
    fhstream f(fname, "rt");
    if(!f.f) { printf("Failed to open hub file: %s\n", fname.c_str()); exit(1); }
    println(hlog, "loading hubs: ", fname);
    while(!feof(f.f)) {
      string l1, l2;
      while(true) {
        int c = fgetc(f.f);
        if(c == EOF) return;
        else if(c == ';') break;
        else if(rv_ignore(c)) ;
        else l1 += c;
        }
      while(true) {
        int c = fgetc(f.f);
        if(c == EOF) return;
        else if(c == ';') return;
        else if(rv_ignore(c)) break;
        else l2 += c;
        }
      if(!id_known(l1)) {
        printf("label unknown: %s\n", l1.c_str());
        exit(1);
        }
      hubval[getid(l1)] = atoi(l2.c_str());
      }
    }
  
  void readsag(const char *fname) {
    maxweight = 0;
    sag_edge = add_edgetype("SAG edge");
    rogueviz::cleanup.push_back([] { sag_edge = nullptr; });
    fhstream f(fname, "rt");
    if(!f.f) {
      printf("Failed to open SAG file: %s\n", fname);
      throw "failed to open SAG file";
      }
    if(informat == 1) {
      scanline(f);
      set<pair<int, int> > edges;
      
      int all = 0, good = 0;
      while(!feof(f.f)) {        
        string l1 = scan<string>(f);
        string l2 = scan<string>(f);
        if(l1 == "") continue;
        if(l2 == "") continue;
        edgeinfo ei(sag_edge);
        ei.i = getid(l1);
        ei.j = getid(l2);
        if(ei.i > ei.j) swap(ei.i, ei.j);
        all++;
        if(edges.count({ei.i, ei.j})) continue;
        good++;
        edges.emplace(ei.i, ei.j);
        ei.weight = 1;
        sagedges.push_back(ei);
        }
      println(hlog, "N = ", isize(vdata), " edges = ", good, "/", all);
      return;
      }
    while(!feof(f.f)) {
      string l1, l2;
      while(true) {
        int c = fgetc(f.f);
        if(c == EOF) return;
        else if(c == ';') break;
        else if(rv_ignore(c)) ;
        else l1 += c;
        }
      while(true) {
        int c = fgetc(f.f);
        if(c == EOF) return;
        else if(c == ';') break;
        else if(rv_ignore(c)) ;
        else l2 += c;
        }
      ld wei;
      if(!scan(f, wei)) continue;
      edgeinfo ei(sag_edge);
      ei.i = getid(l1);
      ei.j = getid(l2);
      ei.weight = wei;
      sagedges.push_back(ei);
      }
    }
  
  ld edgepower=1, edgemul=1;

  void init() {
    rogueviz::init(RV_GRAPH | RV_WHICHWEIGHT | RV_AUTO_MAXWEIGHT | RV_HAVE_WEIGHT);

    rv_hook(rogueviz::hooks_close, 100, [] { sag::sagedges.clear(); });
    rv_hook(shmup::hooks_turn, 100, turn);
    rv_hook(rogueviz::hooks_rvmenu, 100, [] { 
      dialog::addSelItem(XLAT("temperature"), fts(sag::temperature), 't');
      dialog::add_action([] {
        dialog::editNumber(sag::temperature, sag::lowtemp, sag::hightemp, 1, 0, XLAT("temperature"), "");
        });
      dialog::addSelItem(XLAT("SAG mode"), sag::sagmodes[sag::sagmode], 'm');      
      dialog::add_action([] { sag::sagmode = sag::eSagmode( (1+sag::sagmode) % 3 ); });

      dialog::addSelItem(XLAT("min temperature"), fts(sag::lowtemp), 'i');
      dialog::add_action([] {
        dialog::editNumber(sag::lowtemp, -20, 20, 1, 0, XLAT("min temperature"), "");
        });

      dialog::addSelItem(XLAT("max temperature"), fts(sag::hightemp), 'i');
      dialog::add_action([] {
        dialog::editNumber(sag::hightemp, -20, 20, 1, 0, XLAT("high temperature"), "");
        });

      dialog::addSelItem(XLAT("automatic cycle"), fts(sag::vizsa_len), 'c');
      dialog::add_action([] {
        dialog::editNumber(sag::vizsa_len, 5, 1800, 1, 0, XLAT("automatic cycle"), "");
        });
            
      dialog::addBoolItem(XLAT("automatic"), sag::vizsa_start, 'a');
      dialog::add_action([] { 
        sag::vizsa_start = sag::vizsa_start ? 0 : SDL_GetTicks();
        sag::sagmode = sagOff;
        });

      dialog::addBoolItem(XLAT("fix the storage"), sag::vizsa_start, 'A');
      dialog::add_action([] { 
        shmup::fixStorage();
        });

      dialog::addBoolItem(XLAT("forget edges"), sag::vizsa_start, 'B');
      dialog::add_action([] { 
        for(int i=0; i<isize(sagid); i++) forgetedges(i);
        });

      dialog::addBoolItem(XLAT("reassign"), sag::vizsa_start, 'C');
      dialog::add_action([] { 
        reassign();
        });

      dialog::addSelItem(XLAT("smoothness"), its(sag_ittime), 's');
      dialog::add_action([] {
        dialog::editNumber(sag_ittime, 0, 1000, 10, 100, XLAT("smoothness"),
          XLAT("How much milliseconds to compute before re-rendering the screen when optimizing in the background. Low values look nicer, but may cause less time to be spent on iterations.")
          );
        });

      dialog::addBoolItem_action(XLAT("auto-visualize"), sag::auto_visualize, 'b');

      dialog::addBoolItem_action(XLAT("continuous embedding"), sag::embedding, 'e'); 

      if(method == smMatch) {
        dialog::addSelItem(XLAT("match parameter A"), fts(match_a), 'A');
        dialog::add_action([] {
          // old version of RogueViz
          dialog::editNumber(match_a, 0, 10, 1, 1, XLAT("match parameter A"), ""); dialog::reaction = prepare_graph;
          });
        dialog::addSelItem(XLAT("match parameter B"), fts(match_b), 'B');
        dialog::add_action([] {
          // old version of RogueViz
          dialog::editNumber(match_b, 0, 10, 1, 1, XLAT("match parameter B"), ""); dialog::reaction = prepare_graph;
          });
        }

      dialog::addSelItem(XLAT("cost value"), fts(cost), 'X');
      dialog::add_action([] {
        optimize_sag_loglik_auto();
        });
      });

    weight_label = "min weight";
    temperature = 0; sagmode = sagOff;
    }

  void create_viz() {
    int DN = isize(vdata);
    
    for(int i=DN; i<isize(vdata); i++) if(vdata[i].m) vdata[i].m->dead = true;
    for(int i=0; i<isize(vdata); i++) vdata[i].edges.clear();
    vdata.resize(DN);

    if(sagcells[0].first == nullptr) return;

    for(int i=0; i<DN; i++) {
      int ii = i;
      vertexdata& vd = vdata[ii];
      vd.cp = colorpair(dftcolor);

      createViz(ii, sagcells[sagid[i]].first, rgpushxto0(subcell_points[sagcells[sagid[i]].second]));
      }

    for(int i=0; i<DN; i++) vdata[i].data = 0;
    for(int i=0; i<isize(sagedges); i++) {
      edgeinfo& ei = sagedges[i];
      ei.orig = NULL;

      ei.weight2 = pow((double) ei.weight, (double) edgepower) * edgemul;
      
      dmin = 1;
      addedge(ei.i, ei.j, &ei);
      }

    storeall();
    }

  void read(string fn) {
    fname = fn;
    init();
    readsag(fname.c_str());
    if(hub_filename != "")
      read_hubs(hub_filename);
  
    int DN = isize(vdata);
    init_sag_cells_gen();

    int SN = isize(sagcells);
    if(SN < DN) {
      println(hlog, "SN = ", SN, " DN = ", DN);
      throw hr_exception("not enough cells for SAG");
      exit(1);
      }
    sagid.resize(DN);
    for(int i=0; i<DN; i++) sagid[i] = i;
    prepare_graph();
    create_viz();
    println(hlog, "sag::read done");
    }

  void generate_fake_data(int n, int m) {
    init();
    init_sag_cells_gen();

    sagid.resize(n);
    for(int i=0; i<n; i++) sagid[i] = i;
    hrandom_shuffle(sagid);
    if(m > n || m < 0) throw hr_exception("generate_fake_data parameters incorrect");
    sagid.resize(m);
    int SN = isize(sagcells);
    int DN = isize(sagid);
    vdata.resize(DN);
    for(int i=0; i<DN; i++)
      vdata[i].name = its(i) + "@" + its(sagid[i]);

    sag_edge = add_edgetype("SAG edge");
    for(int i=0; i<DN; i++)
    for(int j=i+1; j<DN; j++) {
      edgeinfo ei(sag_edge);
      ei.i = i;
      ei.j = j;
      ei.weight = 1. / sagdist[sagid[i]][sagid[j]];
      sagedges.push_back(ei);
      }

    if(SN < DN) {
      println(hlog, "SN = ", SN, " DN = ", DN);
      throw hr_exception("not enough cells for SAG");
      exit(1);
      }

    prepare_graph();
    create_viz();

    for(int i=0; i<DN; i++) {
      color_t col = patterns::compute_cell_color(sagcells[sagid[i]].first);
      col <<= 8;
      col |= 0xFF;
      vdata[i].cp.color1 = vdata[i].cp.color2 = col;
      }
    }

pair<ld, ld> compute_mAP() {
  int DN = isize(sagid);

  ld meanrank = 0;
  int tgood = 0;
  ld maprank = 0;

  for(int i=0; i<DN; i++) {
    vector<int> alldist(max_sag_dist, 0);
    for(int j=0; j<DN; j++) if(i != j) alldist[sagdist[sagid[i]][sagid[j]]]++;
    vector<int> edgedist(max_sag_dist, 0);
    for(auto j: edges_yes[i]) edgedist[sagdist[sagid[i]][sagid[j]]]++;

    int pgood = 0;
    ld bad = 0;
    ld ap = 0;
    ld pall = 0;

    for(int j=0; j<max_sag_dist; j++) {
      ld good = edgedist[j];
      ld all = alldist[j];
      ld err = all - good;

      bad += err / 2.;
      meanrank += bad * good;
      bad += err / 2.;

      for(int k=0; k<good; k++) {
        pgood++, pall++;
        pall += err/2 / good;
        ap += pgood / pall;
        pall += err/2 / good;
        }
      if(!good) pall += err;
      }

    tgood += pgood;
    if(pgood) maprank += ap / pgood;
    }
  return make_pair(maprank / DN, meanrank / tgood);
  }

int logid;

void geo_stats() {
  start_game();
  init_sag_cells_gen();

  println(hlog, "counting sagdist, N=", int(sagdist.N), " max_sag_dist = ", max_sag_dist);
  vector<short> sgdc(max_sag_dist, 0);
  for(auto x: sagdist) sgdc[x]++;

  println(hlog, "building sorted_sagdist");
  vector<short> sorted_sagdist;
  for(int i=0; i<max_sag_dist; i++) for(int j=0; j<sgdc[i]; j++) sorted_sagdist.push_back(i);

  println(hlog, "computing min_max_nei");
  int minnei = 500, maxnei = 0;
  int SN = sagdist.N;
  for(int i=0; i<SN; i++) for(int j: neighbors[i]) {
    if(sagdist[i][j] < minnei) minnei = sagdist[i][j];
    if(sagdist[i][j] > maxnei) maxnei = sagdist[i][j];
    }   

  for(int i=0; i<3; i++) {
    bool first = false;
    #define out(x, y) if(i == 0) println(hlog, x, " = ", y); else if(first) print(hlog, ";"); first = true; if(i == 1) print(hlog, x); if(i == 2) print(hlog, y);
    out("nodes", SN);
    out("maxsagdist", max_sag_dist);
    out("dim", (euclid && WDIM == 2 && euc::eu.user_axes[1][1] == 1) ? 1 : WDIM);
    out("geometry", S3 >= OINF ? "tree" : hyperbolic ? "hyperbolic" : sphere ? "sphere" : euclid ? "euclid" : nil ? "nil" : sol ? "solv" : mproduct ? "product" : "other");
    out("closed", max_sag_dist == isize(sagcells) ? 0 : closed_manifold ? 1 : 0);
    out("angular", angular);
    for(int p: {1, 10, 50}) { out(format("sagdist%02d", p), sorted_sagdist[(p * sorted_sagdist.size()) / 100]); }
    out("minnei", minnei);
    out("maxnei", maxnei);
    out("neighbors", isize(neighbors[0]));
    println(hlog);
    #undef out
    }
  }

int lastmethod = 0;

int mul_used;

bool known_pairs = false;

void output_stats() {
  if(auto_save != "" && cost < best_cost) {
    println(hlog, "cost ", cost, " beats ", best_cost);
    best_cost = cost;
    sag::save_sag_solution(auto_save);
    }
  println(hlog, "solution: ", sagid);
  int DN = isize(sagid);
  auto [mAP, MeanRank] = compute_mAP();
  dhrg::iddata routing_result;
  if(!known_pairs) { known_pairs = true; dhrg::prepare_pairs(DN, [] (int i) { return edges_yes[i]; }); }
  dhrg::greedy_routing(routing_result, [] (int i, int j) { return sagdist[sagid[i]][sagid[j]]; });
  print(hlog, "CSV;", logid++, ";", isize(sagnode), ";", DN, ";", isize(sagedges), ";", lgsag_pre.R, ";", lgsag_pre.T, ";", lgsag.R, ";", lgsag.T, ";", cost, ";", mAP, ";", MeanRank, ";", routing_result.suc / routing_result.tot, ";", routing_result.routedist / routing_result.bestdist);
  if(lastmethod) print(hlog, ";", lastmethod);
  if(mul_used) print(hlog, ";", mul_used);
  if(report_tempi) print(hlog, ";", hightemp,";",lowtemp,";",format("%lld", numiter));
  println(hlog);
  }

void load_solution(const string& fname) {
  prepare_embedding();
  FILE *f = fopen(fname.c_str(), "rt");
  for(auto& i: sagid) fscanf(f, "%d", &i);
  fclose(f);
  println(hlog, "loaded sagid = ", sagid);

  method = smLogistic;
  lgsag.R = max_sag_dist;
  lgsag.T = 1;
  opt_debug = true;
  twoway = true; allow_doubles = true;
  reassign();
  optimize_sag_loglik_auto();
  }

void find_rt() {
  method = smLogistic;
  lgsag.R = max_sag_dist;
  lgsag.T = 1;
  compute_loglik_tab();
  compute_cost();
  return;

  view_each = 999999;
  int DN = isize(sagid);

  // find poor embedding
  if(1) {
    dynamicval<bool> dad(allow_doubles, false);
    println(hlog, "finding poor embedding");
    sag::dofullsa_iterations(100 * DN);
    println(hlog, "poor embedding successful");
    }
  method = smLogistic;
  // lastmethod = 1;
  lgsag.T = max_sag_dist / 10;
  compute_loglik_tab();
  sag::optimize_sag_loglik_logistic();  
  println(hlog, "poor embedding found: R=", lgsag.R, " T=", lgsag.T);  
  auto poor = lgsag;

  sag::dofullsa_iterations(1500 * DN);
  sag::optimize_sag_loglik_logistic();
  println(hlog, "first iteration: R=", lgsag.R, " T=", lgsag.T);  

  ld minR = 0, maxR = 0;

  println(hlog, "upward search...");
  int sgn = lgsag.R > poor.R ? 1 : -1;

  while(true) {
    ld cR = lgsag.R;
    ld nR = poor.R + (lgsag.R - poor.R) * 1.5;
    lgsag.R = nR;
    compute_loglik_tab(); compute_cost();
    // should_good = true;
    sag::dofullsa_iterations(1500 * DN);
    sag::optimize_sag_loglik_logistic();
    checkmark_cost = HUGE_VAL;
    while(sag::checkmark_hillclimb()) { sag::optimize_sag_loglik_logistic(); }

    println(hlog, "UPWARD: for ", nR, " found R=", lgsag.R, " T=", lgsag.T);
    hlog.flush();
    if(sgn*lgsag.R < sgn*nR) { minR = cR; maxR = lgsag.R; break; }
    }

  if(minR > maxR) swap(minR, maxR);

  println(hlog, "binary search...");
  while(maxR - minR > 1e-3) {
    ld medR = (maxR + minR) / 2;
    lgsag.R = medR;
    compute_loglik_tab(); compute_cost();
    sag::dofullsa_iterations(1500 * DN);
    sag::optimize_sag_loglik_logistic();
    checkmark_cost = HUGE_VAL;
    while(sag::checkmark_hillclimb()) { sag::optimize_sag_loglik_logistic(); }
    println(hlog, "BINARY: for ", medR, " found R=", lgsag.R, " T=", lgsag.T);
    hlog.flush();
    if(lgsag.R < minR) { lgsag.R = minR; break; }
    else if(lgsag.R > maxR) { lgsag.R = maxR; break; }
    else if(lgsag.R > medR) { minR = lgsag.R; }
    else if(lgsag.R < medR) { maxR = lgsag.R; }
    }
  }

vector<pair<dhrg::logistic, ld>> results;

bool optimized_embedding(int mul, ld bonus = 0) {
  if(logid < recover_from) { println(hlog, "skipped ", logid++, " due to recover"); return false; }
  println(hlog, "starting, logid = ", logid, " recover_from = ", recover_from, " R = ", best.R+bonus, " T = ", best.T);
  int DN = isize(sagid);
  mul_used = mul;
  lgsag_pre = best;
  lgsag_pre.R += bonus;
  lgsag = lgsag_pre;
  compute_loglik_tab(); compute_cost();
  dofullsa_iterations(mul * DN);
  optimize_sag_loglik_logistic();
  hlog.flush();
  while(true) {
    int ch = hillclimb();
    println(hlog, "changes = ", ch, " cost = ", cost, " R=", lgsag.R, " T=", lgsag.T);
    if(!ch) break;
    optimize_sag_loglik_logistic();  
    hlog.flush();
    }
  bool new_best = cost < bestcost;
  if(new_best) best = lgsag, bestcost = cost;
  sag::output_stats();
  results.emplace_back(lgsag, cost);
  return new_best;
  }

void sag_new_experiment() {
  view_each = 999999;
  println(hlog, "SAG new experiment started");
  int DN = isize(sagid);
  println(hlog, "N = ", DN);
  hlog.flush();
  twoway = true; allow_doubles = true;

  method = smLogistic;
  if(recover_from) lgsag = best;
  if(!recover_from) lgsag.R = max_sag_dist;
  if(!recover_from) lgsag.T = 1;
  compute_loglik_tab();
  compute_cost();
  best = lgsag; bestcost = HUGE_VAL;

  for(int i=10; i<=1; i--)
    optimized_embedding(15000, i);
  for(int i=1; i<=10; i++)
    optimized_embedding(15000);
  for(int i=1; i<=10; i++)
    optimized_embedding(15000, -i);

  optimized_embedding(24000);
  optimized_embedding(32000);
  optimized_embedding(40000);
  optimized_embedding(48000);
  optimized_embedding(60000);
  optimized_embedding(80000);
  optimized_embedding(100000);
  optimized_embedding(120000);
  optimized_embedding(120000);
  optimized_embedding(120000);

/*
  auto best1 = best;
  for(ld x=-20; x<=20; x+=0.2) {
    best = best1;
    optimized_embedding(30000, x*2);
    } */
  }

void sag_v5() {
  println(hlog, "SAG v5 started");
  int DN = isize(sagid);
  println(hlog, "N = ", DN);
  recost_each = DN; autofix_rt = 2;
  hlog.flush();
  twoway = true; allow_doubles = true;

  method = smLogistic;
  lgsag.R = max_sag_dist;
  lgsag.T = 1;
  compute_loglik_tab();
  compute_cost();
  best = lgsag; bestcost = HUGE_VAL;

  for(int i=0; i<30; i++) optimized_embedding(10000);

  /*
  autofix_rt = 1;
  for(int i=0; i<10; i++) optimized_embedding(10000);

  autofix_rt = 0;
  for(int i=0; i<10; i++) optimized_embedding(10000);
  */
  }

void sag_bridging() {
  println(hlog, "SAG for bridging started");
  int DN = isize(sagid);
  println(hlog, "N = ", DN);
  recost_each = DN; autofix_rt = 2;
  hlog.flush();
  twoway = true; allow_doubles = true;

  method = smLogistic;
  lgsag.R = max_sag_dist;
  lgsag.T = 1;
  compute_loglik_tab();
  compute_cost();
  best = lgsag; bestcost = HUGE_VAL;

  /* find the rough values of R and T */
  optimized_embedding(10000);
  /* use it */
  optimized_embedding(10000);
  }

void sag_getpolar(const char *fname) {
  FILE *f = fopen(fname, "wt");
  for(auto h: sagsubcell_point) {
    fprintf(f, "{%.10lf,%.10lf},\n", hdist0(h), atan2(h));
    }
  fclose(f);
  }

void sag_fulleval() {
  println(hlog, "SAG evaluation started");
  dhrg::N = isize(sagid);
  find_cells();
  println(hlog, "cellpoints:"); fflush(stdout);
  for(int i=0; i<dhrg::N; i++) println(hlog, sagsubcell_point[sagid[i]]);
  println(hlog, "all cellpoints"); fflush(stdout);
  println(hlog, "vdata size = ", isize(vdata));

  dhrg::full_evaluation([] (int i) {
    return hdist(C0, sagsubcell_point[sagid[i]]);
    },
    [] (int i, int j) {
    return hdist(sagsubcell_point[sagid[i]], sagsubcell_point[sagid[j]]);
    },
    GDIM);
  }

void sag_v6() {
  println(hlog, "SAG v6 started");
  int DN = isize(sagid);
  println(hlog, "N = ", DN);
  recost_each = DN; autofix_rt = 2;
  hlog.flush();
  twoway = true; allow_doubles = true;

  method = smLogistic;
  lgsag.R = max_sag_dist;
  lgsag.T = 1;
  compute_loglik_tab();
  compute_cost();
  best = lgsag; bestcost = HUGE_VAL;

  for(int i=0; i<30; i++) optimized_embedding(100000);

  autofix_rt = 1;
  for(int i=0; i<30; i++) optimized_embedding(100000);

  autofix_rt = 0;
  for(int i=0; i<30; i++) optimized_embedding(100000);
  }

void sag_new_experiment_viz() {
  view_each = 999999;
  println(hlog, "SAG new experiment started");
  int DN = isize(sagid);
  println(hlog, "N = ", DN);
  hlog.flush();
  twoway = true; allow_doubles = true;

  method = smLogistic;
  lgsag.R = max_sag_dist;
  lgsag.T = 1;
  compute_loglik_tab();
  compute_cost();
  best = lgsag; bestcost = HUGE_VAL;

  optimized_embedding(15000, 0);
  optimized_embedding(15000, 0);
  }

void auto_orth(bool set_colors) {
  hyperpoint pmin = C0, pmax = C0;
  shiftmatrix M = ggmatrix(centerover);
  ld maxsize = 0;
  println(hlog, "cellpoints = ", isize(cellpoint), " sagsubcell_point = ", isize(sagsubcell_point), " M = ", M);
  for(auto p: sagsubcell_point) {
    hyperpoint ret;
    applymodel(M*p, ret);
    println(hlog, kz(M*p), " -> ", kz(ret));
    for(int i=0; i<3; i++) {
      if(ret[i] < pmin[i]) pmin[i] = ret[i];
      if(ret[i] > pmax[i]) pmax[i] = ret[i];
      }
    ld size = hypot(hypot(ret[0], ret[1]), ret[2]);
    if(size > maxsize) maxsize = size;
    }
  println(hlog, "pmin = ", pmin, " pmax = ", pmax, " maxsize = ", maxsize);
  pconf.scale = 1 / maxsize;
  pconf.clip_min = -maxsize * 3 - 0.1;
  pconf.clip_max = maxsize + 0.1;
  println(hlog, isize(vdata), " vs ", isize(sagsubcell_point));
  if(set_colors) for(int i=0; i<isize(vdata); i++) {
    auto p = sagsubcell_point[sagid[i]];
    hyperpoint ret;
    applymodel(M*p, ret);
    auto& col = vdata[i].cp.color1;
    for(int j=0; j<3; j++) {
      println(hlog, "coloring ", tie(i,j), ret[j], " -> ", ilerp(pmin[j], pmax[j], ret[j]), " -> ", lerp(0, 255, ilerp(pmin[j], pmax[j], ret[j])));
      part(col, j+1) = lerp(0, 255, ilerp(pmin[j], pmax[j], ret[j]));
      }
    vdata[i].cp.color2 = col;
    }
  }

set<cell*> cells_to_draw_bak;

void autoviz() {
  no_find_player = true;
  smooth_scrolling = true;
  // bobbing = false; // older version of RogueViz
  View = Id;
  models::configure();
  drawthemap();
  if(GDIM == 3) game_keys_scroll = true;
  if(sphere && GDIM == 3) {
    pmodel = mdEquidistant;
    pconf.scale = 0.95;
    }
  if(hyperbolic && GDIM == 2) {
    pmodel = mdDisk;
    pconf.alpha = 1;
    pconf.scale = 0.95;
    }
  if(hyperbolic && GDIM == 3) {
    pmodel = mdDisk;
    pconf.alpha = 1;
    pconf.scale = 0.95;
    }
  if(sol || nil) {
    pmodel = mdLieOrthogonal;
    nisot::geodesic_movement = false;
    auto_orth(false);
    }
  if(sl2) {
    pmodel = mdHorocyclic;
    auto_orth(false);
    }
  if(gproduct) {
    pmodel = mdEquidistant;
    auto_orth(false);
    }
  if(euclid) {
    pmodel = mdDisk;
    pconf.alpha = 1;
    auto_orth(false);
    }
  if(quotient) {
    if(GDIM == 3) {
      pmodel = mdPerspective;
      if(hyperbolic) pmodel = mdDisk;
      frustum_culling = false;
      if(euclid) camera_speed *= 10;
      }
    return;
    }
  // older version of RogueViz
  // cells_to_draw.clear();
  // for(auto p: sagcells) cells_to_draw.insert(p.first);
  // rv_hook(hooks_o_key, 80, [] (o_funcs& v) { v.push_back(named_functionality("swap", [] { swap(cells_to_draw, cells_to_draw_bak); })); });
  }

void write_colors(string s) {
  auto_orth(true);
  if(s == "-") return;
  fhstream f(s, "wt");
  for(int i=0; i<isize(vdata); i++) {
    println(f, vdata[i].name, ",", format("%8x", vdata[i].cp.color1));
    }
  }

void sag_test_mul() {
  allow_doubles = true; twoway = true;
  int DN = isize(sagid);
  // lgsag.R=9.19925; lgsag.T=0.587723;
  method = smLogistic;
  lgsag.R = max_sag_dist;
  lgsag.T = 1;
  compute_loglik_tab();
  compute_cost();
  best = lgsag; bestcost = 999999;
  recost_each = DN; autofix_rt = 2;
  output_fullsa = false;

  println(hlog, "sag_test_mul started");

  if(1) for(int mul=25;; mul *= 2) for(int af: {3, 2, 1, 0}) {
    autofix_rt = af;
    ld tcost = 0;
    ld tcost2 = 0;
    int qty = 100;
    vector<ld> costs;
    // println(hlog, "R=", best.R, " T=", best.T);
    for(int i=0; i<qty; i++) {
      println(hlog, tie(mul, af, i));
      lgsag = best; compute_loglik_tab(); compute_cost();
      sag::dofullsa_iterations(mul * DN);
      sag::optimize_sag_loglik_logistic();
      checkmark_cost = HUGE_VAL;
      while(sag::checkmark_hillclimb()) sag::optimize_sag_loglik_logistic();
      tcost += cost;
      tcost2 += cost * cost;
      costs.push_back(cost);
      bool new_best = cost < bestcost;
      if(new_best) best = lgsag, bestcost = cost;
      // println(hlog, "CSV;", mul, ";", af, ";", i, ";", cost);
      }
    sort(costs.begin(), costs.end());
    println(hlog, "mul=", mul, " AF=", autofix_rt, " ECost=", tcost/qty, " sigma Cost = ", sqrt(tcost2/qty - tcost*tcost/qty/qty), " : ", costs);
    }

  auto tpair = [&] (int lt, int ht) {
    ld tcost = 0;

    for(int i=0; i<20; i++) {

      sagnode.clear();
      sagnode.resize(isize(sagcells), -1);
      for(int i=0; i<DN; i++) sagid[i] = i;
      for(int i=0; i<DN; i++) sagnode[i] = i;
      compute_cost();

      lowtemp = 20;
      hightemp = 20;
      sag::dofullsa_iterations(20 * DN);

      lowtemp = lt;
      hightemp = ht;
      sag::dofullsa_iterations(1000 * DN);
      tcost += cost;
      }
    println(hlog, "lt=", lt, " ht=", ht, " tcost=", tcost);    
    };

  if(false)
  for(int lt: {-3, -5, -10, -15, -20, -25})
  for(int ht: {-2, -1, 0, 1, 2, 5, 10})
    tpair(lt, ht);
  }

int readArgs() {
#if CAP_COMMANDLINE
  using namespace arg;

  if(0) ;

  else if(argis("-sagmin")) {
    auto& ed = sag_edge ? *sag_edge : default_edgetype;
    shift_arg_formula(ed.visible_from);
    ed.visible_from_hi = ed.visible_from;
    }
  else if(argis("-sagminhi")) {
    auto& ed = sag_edge ? *sag_edge : default_edgetype;
    shift_arg_formula(ed.visible_from_hi);
    }
  else if(argis("-sag_gdist")) {
    shift(); sag::gdist_prec = argi();
    }
  else if(argis("-sag_gdist_dijkstra")) {
    shift(); sag::dijkstra_maxedge = argi(); sag::dijkstra_tile = false;
    }
  else if(argis("-sag-dtile")) {
    sag::dijkstra_tile = true; sag::dijkstra_maxedge = 1;
    }
  else if(argis("-sag_gdist_save")) {
    shift();
    sagdist.save(args());
    }
  else if(argis("-sag_gdist_load")) {
    shift(); distance_file = args();
    }

  else if(argis("-sagrt")) {
    shift(); sag::lgsag.R = argf();
    shift(); sag::lgsag.T = argf();
    if(method == smLogistic) compute_loglik_tab();
    }

  else if(argis("-sagmatch-ab")) {
    shift(); sag::match_a = argf();
    shift(); sag::match_b = argf();
    if(method == smMatch) prepare_graph();
    }

  else if(argis("-sagrt-auto")) {
    compute_auto_rt();
    }

  else if(argis("-sag_use_loglik")) {  
    shift(); int mtd = argi();
    if(mtd == 0) method = smClosest, loglik_repeat = false;
    if(mtd == 1) method = smLogistic, loglik_repeat = false;
    if(mtd == 2) method = smLogistic, loglik_repeat = true;
    if(mtd == 3) method = smMatch, loglik_repeat = false;
    if(mtd == 4) method = smMatch, loglik_repeat = true;
    if(method == smLogistic)
      compute_loglik_tab();
    if(method == smMatch) prepare_graph();
    }

  else if(argis("-sagformat")) {
    shift(); informat = argi();
    }

// (1) configure edge weights
  else if(argis("-sag-edgepower")) {
    shift_arg_formula(sag::edgepower);
    shift_arg_formula(sag::edgemul);
    }
// (1) configure temperature (high, low)
  else if(argis("-sagtemp")) {
    shift(); sag::hightemp = argi();
    shift(); sag::lowtemp = argi();
    }
// (2) read the edge data
  else if(argis("-sagpar")) {
    PHASE(3);
    shift();
    sag::sagpar = argi();
    }
  else if(argis("-sag")) {
    PHASE(3); 
    shift(); sag::read(args());
    }
  else if(argis("-sagfake")) {
    PHASE(3);
    shift(); int n = argi();
    shift(); int m = argi();
    sag::generate_fake_data(n, m);
    }
  else if(argis("-sagaviz")) {
    PHASE(3); 
    shift(); sag::auto_visualize = argi();
    }
  else if(argis("-saghubs")) {
    println(hlog, "HUBS");
    PHASE(3); 
    shift_arg_formula(sag::hub_penalty);
    shift(); hub_filename = args();
    }
// (3) load the initial positioning
  else if(argis("-sagload")) {
    PHASE(3); shift(); sag::load_sag_solution(args());
    }
// (4) perform simulated annealing: -fullsa <time in seconds>
  else if(argis("-sagfull")) {
    shift(); sag::dofullsa(argf());
    }
  else if(argis("-sagfulli")) {
    shift(); sag::dofullsa_iterations(argll());
    }
  else if(argis("-sagviz")) {
    sag::vizsa_start = SDL_GetTicks();
    shift(); sag::vizsa_len = argi();
    }
  else if(argis("-sagsmooth")) {
    shift(); sag::sag_ittime = argi();
    }
  else if(argis("-sagstats")) {
    output_stats();
    }
  else if(argis("-sag-angular")) {
    shift(); angular = argi();
    }
  else if(argis("-sagstats-logid")) {
    shift(); logid = argi();
    }
// (5) save the positioning
  else if(argis("-sagsave")) {
    PHASE(3); shift(); sag::save_sag_solution(args());
    }
  else if(argis("-sagsave-auto")) {
    PHASE(3); shift(); auto_save = args();
    }
// (6) output loglikelihood
  else if(argis("-sagloglik-l")) {
    sag::optimize_sag_loglik_logistic();
    }
  else if(argis("-sagloglik-m")) {
    sag::optimize_sag_loglik_match();
    }
  else if(argis("-sagloglik-a")) {
    sag::optimize_sag_loglik_auto();
    }
  else if(argis("-sagmode")) {
    shift();
    vizsa_start = 0;
    sagmode = (eSagmode) argi();
    if(sagmode == sagSA) {
      shift(); temperature = argf();
      }
    }
  else if(argis("-sagembed")) {
    sag::embedding = true;
    }
  else if(argis("-sag0")) {
    sag::report_tempi = true;
    numiter = 0;
    }
  else if(argis("-sagembedoff")) {
    sag::embedding = false;
    }
  else if(argis("-sagsavee")) {
    PHASE(3); shift(); sag::save_embedding(args());
    }
  else if(argis("-sagloade")) {
    PHASE(3); shift(); sag::load_embedding(args());
    }
  else if(argis("-sag-geo-stats")) geo_stats();
  else if(argis("-sag-new")) sag_new_experiment();
  else if(argis("-sag-v5")) sag_v5();
  else if(argis("-sag-v6")) sag_v6();
  else if(argis("-sag-new-viz")) sag_new_experiment_viz();
  else if(argis("-sag-test-mul")) sag_test_mul();

  else if(argis("-sag-creq")) {
   shift(); cell_request = argi();
   }

  else if(argis("-sag-load-solution")) {
    shift(); load_solution(args());
    }
  else if(argis("-sag-init")) {
    start_game();
    init_sag_cells_gen();
    }
  else if(argis("-sag-recover")) {
    shift(); best.R = argf();
    shift(); best.T = argf();
    shift(); bestcost = argf();
    shift(); recover_from = argi();
    println(hlog, "set recover_from to ", recover_from);
    // 58.6509;7.08961;26492.1
    }

  else if(argis("-gen-subcellpoints")) {
    // solv only!
    generate_subcellpoints();
    // rogueviz::addHook(hooks_drawcell, 100, viz_subcellpoints);
    }

  else if(argis("-sagcr-viz")) {
    rogueviz::addHook(hooks_drawcell, 100, viz_sagcr);
    }
  else if(argis("-sagcr-viz-clear")) {
    shmup::monstersAt.clear();
    }
  else if(argis("-sag-autoviz")) {
    autoviz();
    }
  else if(argis("-sag-write-colors")) {
    shift(); write_colors(args());
    }

  else if(argis("-sag-bridging")) sag_bridging();
  else if(argis("-sag-fulleval")) sag_fulleval();

  else if(argis("-sag-getpolar")) { shift(); sag_getpolar(argcs()); }

  else return 1;
#endif
  return 0;
  }

int ah = addHook(hooks_args, 100, readArgs);

bool turn(int delta) {
  if(vizsa_start) {
    if(vizsa_start == -1) vizsa_start = ticks;
    auto t = ticks;
    double d = (t-vizsa_start) / (1000. * vizsa_len);
    if(d > 1 && loglik_repeat) {
      optimize_sag_loglik_auto();
      output_stats();
      vizsa_start = -1;
      }
    if(d > 1) sagmode = sagOff;
    else {
      temperature = hightemp - (d*(hightemp-lowtemp));
      sagmode = sagSA;
      }
    }
  if(sagmode == sagOff && embedding) {
    embedding_iterate();
    }

  iterate();

  return false;
  // shmup::pc[0]->rebase();
  }

EX }

}
