/////////////////////////////////////////////////
// Forest Fire
void TForestFire::InfectAll() {
  InfectNIdV.Gen(Graph->GetNodes());
  for (TNGraph::TNodeI NI = Graph->BegNI(); NI < Graph->EndNI(); NI++) {
    InfectNIdV.Add(NI.GetId()); }
}

void TForestFire::InfectRnd(const int& NInfect) {
  IAssert(NInfect < Graph->GetNodes());
  TIntV NIdV(Graph->GetNodes(), 0);
  for (TNGraph::TNodeI NI = Graph->BegNI(); NI < Graph->EndNI(); NI++) {
    NIdV.Add(NI.GetId()); }
  NIdV.Shuffle(Rnd);
  InfectNIdV.Gen(NInfect, 0);
  for (int i = 0; i < NInfect; i++) {
    InfectNIdV.Add(NIdV[i]); }
}

// burn each link independently (forward with FwdBurnProb, backward with BckBurnProb)
void TForestFire::BurnExpFire() {
  const double OldFwdBurnProb = FwdBurnProb;
  const double OldBckBurnProb = BckBurnProb;
  const int NInfect = InfectNIdV.Len();
  const TNGraph& G = *Graph;
  TIntH BurnedNIdH;               // burned nodes
  TIntV BurningNIdV = InfectNIdV; // currently burning nodes
  TIntV NewBurnedNIdV;            // nodes newly burned in current step
  bool HasAliveNbrs;              // has unburned neighbors
  int NBurned = NInfect, NDiedFire=0;
  for (int i = 0; i < InfectNIdV.Len(); i++) {
    BurnedNIdH.AddDat(InfectNIdV[i]); }
  NBurnedTmV.Clr(false);  NBurningTmV.Clr(false);  NewBurnedTmV.Clr(false);
  for (int time = 0; ; time++) {
    NewBurnedNIdV.Clr(false);
    // for each burning node
    for (int node = 0; node < BurningNIdV.Len(); node++) {
      const int& BurningNId = BurningNIdV[node];
      const TNGraph::TNodeI Node = G.GetNI(BurningNId);
      HasAliveNbrs = false;
      NDiedFire = 0;
      // burn forward links  (out-links)
      for (int e = 0; e < Node.GetOutDeg(); e++) {
        const int OutNId = Node.GetOutNId(e);
        if (! BurnedNIdH.IsKey(OutNId)) { // not yet burned
          HasAliveNbrs = true;
          if (Rnd.GetUniDev() < FwdBurnProb) {
            BurnedNIdH.AddDat(OutNId);  NewBurnedNIdV.Add(OutNId);  NBurned++; }
        }
      }
      // burn backward links (in-links)
      if (BckBurnProb > 0.0) {
        for (int e = 0; e < Node.GetInDeg(); e++) {
          const int InNId = Node.GetInNId(e);
          if (! BurnedNIdH.IsKey(InNId)) { // not yet burned
            HasAliveNbrs = true;
            if (Rnd.GetUniDev() < BckBurnProb) {
              BurnedNIdH.AddDat(InNId);  NewBurnedNIdV.Add(InNId);  NBurned++; }
          }
        }
      }
      if (! HasAliveNbrs) { NDiedFire++; }
    }
    NBurnedTmV.Add(NBurned);
    NBurningTmV.Add(BurningNIdV.Len() - NDiedFire);
    NewBurnedTmV.Add(NewBurnedNIdV.Len());
    //BurningNIdV.AddV(NewBurnedNIdV);   // node is burning eternally
    BurningNIdV.Swap(NewBurnedNIdV);    // node is burning just 1 time step
    if (BurningNIdV.Empty()) break;
    FwdBurnProb = FwdBurnProb * ProbDecay;
    BckBurnProb = BckBurnProb * ProbDecay;
  }
  BurnedNIdV.Gen(BurnedNIdH.Len(), 0);
  for (int i = 0; i < BurnedNIdH.Len(); i++) {
    BurnedNIdV.Add(BurnedNIdH.GetKey(i)); }
  FwdBurnProb = OldFwdBurnProb;
  BckBurnProb = OldBckBurnProb;
}

// Node selects N~geometric(1.0-FwdBurnProb)-1 out-links and burns them. Then same for in-links.
// geometirc(p) has mean 1/(p), so for given FwdBurnProb, we burn 1/(1-FwdBurnProb)
void TForestFire::BurnGeoFire() {
  const double OldFwdBurnProb=FwdBurnProb;
  const double OldBckBurnProb=BckBurnProb;
  const int& NInfect = InfectNIdV.Len();
  const TNGraph& G = *Graph;
  TIntH BurnedNIdH;               // burned nodes
  TIntV BurningNIdV = InfectNIdV; // currently burning nodes
  TIntV NewBurnedNIdV;            // nodes newly burned in current step
  bool HasAliveInNbrs, HasAliveOutNbrs; // has unburned neighbors
  TIntV AliveNIdV;                // NIds of alive neighbors
  int NBurned = NInfect, time;
  for (int i = 0; i < InfectNIdV.Len(); i++) {
    BurnedNIdH.AddDat(InfectNIdV[i]); }
  NBurnedTmV.Clr(false);  NBurningTmV.Clr(false);  NewBurnedTmV.Clr(false);
  for (time = 0; ; time++) {
    NewBurnedNIdV.Clr(false);
    for (int node = 0; node < BurningNIdV.Len(); node++) {
      const int& BurningNId = BurningNIdV[node];
      const TNGraph::TNodeI Node = G.GetNI(BurningNId);
      // find unburned links
      HasAliveOutNbrs = false;
      AliveNIdV.Clr(false); // unburned links
      for (int e = 0; e < Node.GetOutDeg(); e++) {
        const int OutNId = Node.GetOutNId(e);
        if (! BurnedNIdH.IsKey(OutNId)) {
          HasAliveOutNbrs = true;  AliveNIdV.Add(OutNId); }
      }
      // number of links to burn (geometric coin). Can also burn 0 links
      const int BurnNFwdLinks = Rnd.GetGeoDev(1.0-FwdBurnProb) - 1;
      if (HasAliveOutNbrs && BurnNFwdLinks > 0) {
        AliveNIdV.Shuffle(Rnd);
        for (int i = 0; i < TMath::Mn(BurnNFwdLinks, AliveNIdV.Len()); i++) {
          BurnedNIdH.AddDat(AliveNIdV[i]);
          NewBurnedNIdV.Add(AliveNIdV[i]);  NBurned++; }
      }
      // backward links
      if (BckBurnProb > 0.0) {
        // find unburned links
        HasAliveInNbrs = false;
        AliveNIdV.Clr(false);
        for (int e = 0; e < Node.GetInDeg(); e++) {
          const int InNId = Node.GetInNId(e);
          if (! BurnedNIdH.IsKey(InNId)) {
            HasAliveInNbrs = true;  AliveNIdV.Add(InNId); }
        }
         // number of links to burn (geometric coin). Can also burn 0 links
        const int BurnNBckLinks = Rnd.GetGeoDev(1.0-BckBurnProb) - 1;
        if (HasAliveInNbrs && BurnNBckLinks > 0) {
          AliveNIdV.Shuffle(Rnd);
          for (int i = 0; i < TMath::Mn(BurnNBckLinks, AliveNIdV.Len()); i++) {
            BurnedNIdH.AddDat(AliveNIdV[i]);
            NewBurnedNIdV.Add(AliveNIdV[i]);  NBurned++; }
        }
      }
    }
    NBurnedTmV.Add(NBurned);  NBurningTmV.Add(BurningNIdV.Len());  NewBurnedTmV.Add(NewBurnedNIdV.Len());
    // BurningNIdV.AddV(NewBurnedNIdV);   // node is burning eternally
    BurningNIdV.Swap(NewBurnedNIdV);   // node is burning just 1 time step
    if (BurningNIdV.Empty()) break;
    FwdBurnProb = FwdBurnProb * ProbDecay;
    BckBurnProb = BckBurnProb * ProbDecay;
  }
  BurnedNIdV.Gen(BurnedNIdH.Len(), 0);
  for (int i = 0; i < BurnedNIdH.Len(); i++) {
    BurnedNIdV.Add(BurnedNIdH.GetKey(i)); }
  FwdBurnProb = OldFwdBurnProb;
  BckBurnProb = OldBckBurnProb;
}

/*// exact implementation of the algorithm described in KDD '05 paper
void TForestFire::BurnGeoFire() {
  const double OldFwdBurnProb=FwdBurnProb;
  const double OldBckBurnProb=BckBurnProb;
  const double ProbRatio = BckBurnProb/FwdBurnProb;
  const int NInfect = InfectNIdV.Len();
  const TNGraph& G = *Graph;
  TIntH BurnedNIdH;               // burned nodes
  TIntV BurningNIdV = InfectNIdV; // currently burning nodes
  TIntV NewBurnedNIdV;            // nodes newly burned in current step
  bool HasAliveInNbrs, HasAliveOutNbrs; // has unburned neighbors
  TIntV AliveNIdV;                // NIds of alive neighbors
  int NBurned = NInfect, time;
  for (int i = 0; i < InfectNIdV.Len(); i++) {
    BurnedNIdH.AddDat(InfectNIdV[i]); }
  NBurnedTmV.Clr(false);  NBurningTmV.Clr(false);  NewBurnedTmV.Clr(false);
  for (time = 0; ; time++) {
    NewBurnedNIdV.Clr(false);
    for (int node = 0; node < BurningNIdV.Len(); node++) {
      const int& BurningNId = BurningNIdV[node];
      const int BurnNLinks = Rnd.GetGeoDev(1.0-(FwdBurnProb)) - 1;
      const TNGraph::TNode& Node = G.GetNode(BurningNId);
      // find unburned links
      if (BurnNLinks > 0) {
        AliveNIdV.Clr(false);
        for (int e = 0; e < Node.GetOutDeg(); e++) {
          const int OutNId = Node.GetOutNId(e);
          if (! BurnedNIdH.IsKey(OutNId)) { HasAliveOutNbrs=true;  AliveNIdV.Add(OutNId); }
        }
        for (int e = 0; e < Node.GetInDeg(); e++) {
          const int InNId = Node.GetInNId(e);
          if (! BurnedNIdH.IsKey(InNId)) { HasAliveInNbrs=true;  AliveNIdV.Add(-InNId); }
        }
        AliveNIdV.Shuffle(Rnd);
        // add links
        for (int e = i; i < AliveNIdV.Len(); i++) {
          if (AliveNIdV[i] > 0) BurnedNIdH.AddDat(AliveNIdV[i]);
          NewBurnedNIdV.Add(AliveNIdV[i]);  NBurned++;
        }
      }
      HasAliveOutNbrs = false;
      AliveNIdV.Clr(false); // unburned links
      for (int e = 0; e < Node.GetOutDeg(); e++) {
        const int OutNId = Node.GetOutNId(e);
        if (! BurnedNIdH.IsKey(OutNId)) {
          HasAliveOutNbrs = true;  AliveNIdV.Add(OutNId); }
      }
      // number of links to burn (geometric coin). Can also burn 0 links
      const int BurnNFwdLinks = Rnd.GetGeoDev(1.0-FwdBurnProb) - 1;
      if (HasAliveOutNbrs && BurnNFwdLinks > 0) {
        AliveNIdV.Shuffle(Rnd);
        for (int i = 0; i < TMath::Mn(BurnNFwdLinks, AliveNIdV.Len()); i++) {
          BurnedNIdH.AddDat(AliveNIdV[i]);
          NewBurnedNIdV.Add(AliveNIdV[i]);  NBurned++; }
      }
      // backward links
      if (BckBurnProb > 0.0) {
        // find unburned links
        HasAliveInNbrs = false;
        AliveNIdV.Clr(false);
        for (int e = 0; e < Node.GetInDeg(); e++) {
          const int InNId = Node.GetInNId(e);
          if (! BurnedNIdH.IsKey(InNId)) {
            HasAliveInNbrs = true;  AliveNIdV.Add(InNId); }
        }
         // number of links to burn (geometric coin). Can also burn 0 links
        const int BurnNBckLinks = Rnd.GetGeoDev(1.0-BckBurnProb) - 1;
        if (HasAliveInNbrs && BurnNBckLinks > 0) {
          AliveNIdV.Shuffle(Rnd);
          for (int i = 0; i < TMath::Mn(BurnNBckLinks, AliveNIdV.Len()); i++) {
            BurnedNIdH.AddDat(AliveNIdV[i]);
            NewBurnedNIdV.Add(AliveNIdV[i]);  NBurned++; }
        }
      }
    }
    NBurnedTmV.Add(NBurned);  NBurningTmV.Add(BurningNIdV.Len());  NewBurnedTmV.Add(NewBurnedNIdV.Len());
    // BurningNIdV.AddV(NewBurnedNIdV);   // node is burning eternally
    BurningNIdV.Swap(NewBurnedNIdV);   // node is burning just 1 time step
    if (BurningNIdV.Empty()) break;
    FwdBurnProb = FwdBurnProb * ProbDecay;
    BckBurnProb = BckBurnProb * ProbDecay;
  }
  BurnedNIdV.Gen(BurnedNIdH.Len(), 0);
  for (int i = 0; i < BurnedNIdH.Len(); i++) {
    BurnedNIdV.Add(BurnedNIdH.GetKey(i)); }
  FwdBurnProb = OldFwdBurnProb;
  BckBurnProb = OldBckBurnProb;
}//*/

void TForestFire::PlotFire(const TStr& FNmPref, const TStr& Desc, const bool& PlotAllBurned) {
  TGnuPlot GnuPlot(FNmPref, TStr::Fmt("%s. ForestFire. G(%d, %d). Fwd:%g  Bck:%g  NInfect:%d",
    Desc.CStr(), Graph->GetNodes(), Graph->GetEdges(), FwdBurnProb(), BckBurnProb(), InfectNIdV.Len()));
  GnuPlot.SetXYLabel("TIME EPOCH", "Number of NODES");
  if (PlotAllBurned) GnuPlot.AddPlot(NBurnedTmV, gpwLinesPoints, "All burned nodes till time");
  GnuPlot.AddPlot(NBurningTmV, gpwLinesPoints, "Burning nodes at time");
  GnuPlot.AddPlot(NewBurnedTmV, gpwLinesPoints, "Newly burned nodes at time");
  GnuPlot.SavePng(TFile::GetUniqueFNm(TStr::Fmt("fireSz.%s_#.png", FNmPref.CStr())));
}

PNGraph TForestFire::GenGraph(const int& Nodes, const double& FwdProb, const double& BckProb) {
  TFfGGen Ff(false, 1, FwdProb, BckProb, 1.0, 0.0, 0.0);
  Ff.GenGraph(Nodes);
  return Ff.GetGraph();
}

/////////////////////////////////////////////////
// Forest Fire Graph Generator
int TFfGGen::TimeLimitSec = 30*60; // 30 minutes

TFfGGen::TFfGGen(const bool& BurnExpFireP, const int& StartNNodes, const double& ForwBurnProb,
                 const double& BackBurnProb, const double& DecayProb, const double& Take2AmbasPrb, const double& OrphanPrb) :
 Graph(), BurnExpFire(BurnExpFireP), StartNodes(StartNNodes), FwdBurnProb(ForwBurnProb),
 BckBurnProb(BackBurnProb), ProbDecay(DecayProb), Take2AmbProb(Take2AmbasPrb), OrphanProb(OrphanPrb) {
  //IAssert(ProbDecay == 1.0); // do not need Decay any more
}

TStr TFfGGen::GetParamStr() const {
  return TStr::Fmt("%s  FWD:%g  BCK:%g, StartNds:%d, Take2:%g, Orphan:%g, ProbDecay:%g",
    BurnExpFire?"EXP":"GEO", FwdBurnProb(), BckBurnProb(), StartNodes(), Take2AmbProb(), OrphanProb(), ProbDecay());
}

TFfGGen::TStopReason TFfGGen::AddNodes(const int& GraphNodes, const bool& FloodStop) {
  printf("\n***ForestFire:  %s  Nodes:%d  StartNodes:%d  Take2AmbProb:%g\n", BurnExpFire?"ExpFire":"GeoFire", GraphNodes, StartNodes(), Take2AmbProb());
  printf("                FwdBurnP:%g  BckBurnP:%g  ProbDecay:%g  Orphan:%g\n", FwdBurnProb(), BckBurnProb(), ProbDecay(), OrphanProb());
  TExeTm ExeTm;
  int Burned1=0, Burned2=0, Burned3=0; // last 3 fire sizes
  // create initial set of nodes
  if (Graph.Empty()) { Graph = PNGraph::New(); }
  if (Graph->GetNodes() == 0) {
    for (int n = 0; n < StartNodes; n++) { Graph->AddNode(); }
  }
  int NEdges = Graph->GetEdges();
  // forest fire
  TRnd Rnd(0);
  TForestFire ForestFire(Graph, FwdBurnProb, BckBurnProb, ProbDecay, 0);
  // add nodes
  for (int NNodes = Graph->GetNodes()+1; NNodes <= GraphNodes; NNodes++) {
    const int NewNId = Graph->AddNode(-1);
    IAssert(NewNId == Graph->GetNodes()-1); // node ids have to be 0...N
    // not an Orphan (burn fire)
    if (OrphanProb == 0.0 || Rnd.GetUniDev() > OrphanProb) {
      // infect ambassadors
      if (Take2AmbProb == 0.0 || Rnd.GetUniDev() > Take2AmbProb || NewNId < 2) {
        ForestFire.Infect(Rnd.GetUniDevInt(NewNId)); // take 1 ambassador
      } else {
        const int AmbassadorNId1 = Rnd.GetUniDevInt(NewNId);
        int AmbassadorNId2 = Rnd.GetUniDevInt(NewNId);
        while (AmbassadorNId1 == AmbassadorNId2) {
          AmbassadorNId2 = Rnd.GetUniDevInt(NewNId); }
        ForestFire.Infect(TIntV::GetV(AmbassadorNId1, AmbassadorNId2)); // take 2 ambassadors
      }
      // burn fire
      if (BurnExpFire) { ForestFire.BurnExpFire(); }
      else { ForestFire.BurnGeoFire(); }
      // add edges to burned nodes
      for (int e = 0; e < ForestFire.GetBurned(); e++) {
        Graph->AddEdge(NewNId, ForestFire.GetBurnedNId(e));
        NEdges++;
      }
      Burned1=Burned2;  Burned2=Burned3;  Burned3=ForestFire.GetBurned();
    } else {
      // Orphan (zero out-links)
      Burned1=Burned2;  Burned2=Burned3;  Burned3=0;
    }
    if (NNodes % Kilo(1) == 0) {
      printf("(%d, %d)  burned: [%d,%d,%d]  [%s]\n", NNodes, NEdges, Burned1, Burned2, Burned3, ExeTm.GetStr()); }
    if (FloodStop && NEdges>GraphNodes && (NEdges/double(NNodes)>1000.0)) { // average node degree is more than 500
      printf(". FLOOD. G(%6d, %6d)\n", NNodes, NEdges);  return srFlood; }
    if (NNodes % 1000 == 0 && TimeLimitSec > 0 && ExeTm.GetSecs() > TimeLimitSec) {
      printf(". TIME LIMIT. G(%d, %d)\n", Graph->GetNodes(), Graph->GetEdges());
      return srTimeLimit; }
  }
  IAssert(Graph->GetEdges() == NEdges);
  return srOk;
}

TFfGGen::TStopReason TFfGGen::GenGraph(const int& GraphNodes, const bool& FloodStop) {
  Graph = PNGraph::New();
  return AddNodes(GraphNodes, FloodStop);
}

TFfGGen::TStopReason TFfGGen::GenGraph(const int& GraphNodes, PGStatVec& EvolStat, const bool& FloodStop) {
  int GrowthStatNodes = 100;
  Graph = PNGraph::New();
  AddNodes(StartNodes);
  TStopReason SR = srUndef;
  while (Graph->GetNodes() < GraphNodes) {
    SR = AddNodes(GrowthStatNodes, FloodStop);
    if (SR != srOk) { return SR; }
    EvolStat->Add(Graph, TSecTm(Graph->GetNodes()));
    GrowthStatNodes = int(1.5*GrowthStatNodes);
  }
  return SR;
}

void TFfGGen::PlotFireSize(const TStr& FNmPref, const TStr& DescStr) {
  TGnuPlot GnuPlot("fs."+FNmPref, TStr::Fmt("%s. Fire size. G(%d, %d)",
    DescStr.CStr(), Graph->GetNodes(), Graph->GetEdges()));
  GnuPlot.SetXYLabel("Vertex id (iterations)", "Fire size (node out-degree)");
  TFltPrV IdToOutDegV;
  for (TNGraph::TNodeI NI = Graph->BegNI(); NI < Graph->EndNI(); NI++) {
    IdToOutDegV.Add(TFltPr(NI.GetId(), NI.GetOutDeg())); }
  IdToOutDegV.Sort();
  GnuPlot.AddPlot(IdToOutDegV, gpwImpulses, "Node out-degree");
  GnuPlot.SavePng();
}

void TFfGGen::GenFFGraphs(const double& FProb, const double& BProb, const TStr& FNm) {
  const int NRuns = 10;
  const int NNodes = 10000;
  TGStat::NDiamRuns = 10;
  //const double FProb = 0.35, BProb = 0.20;  // ff1
  //const double FProb = 0.37, BProb = 0.32;  // ff2
  //const double FProb = 0.37, BProb = 0.325; // ff22
  //const double FProb = 0.37, BProb = 0.33;  // ff3
  //const double FProb = 0.37, BProb = 0.35;  // ff4
  //const double FProb = 0.38, BProb = 0.35;  // ff5
  TVec<PGStatVec> GAtTmV;
  TFfGGen FF(false, 1, FProb, BProb, 1.0, 0, 0);
  for (int r = 0; r < NRuns; r++) {
    PGStatVec GV = TGStatVec::New(tmuNodes, TGStat::AllStat());
    FF.GenGraph(NNodes, GV, true);
    for (int i = 0; i < GV->Len(); i++) {
      if (i == GAtTmV.Len()) {
        GAtTmV.Add(TGStatVec::New(tmuNodes, TGStat::AllStat()));
      }
      GAtTmV[i]->Add(GV->At(i));
    }
    IAssert(GAtTmV.Len() == GV->Len());
  }
  PGStatVec AvgStat = TGStatVec::New(tmuNodes, TGStat::AllStat());
  for (int i = 0; i < GAtTmV.Len(); i++) {
    AvgStat->Add(GAtTmV[i]->GetAvgGStat(false));
  }
  AvgStat->PlotAllVsX(gsvNodes, FNm, TStr::Fmt("Forest Fire: F:%g  B:%g (%d runs)", FProb, BProb, NRuns));
  AvgStat->Last()->PlotAll(FNm, TStr::Fmt("Forest Fire: F:%g  B:%g (%d runs)", FProb, BProb, NRuns));
}

/*/////////////////////////////////////////////////
// Forest Fire Phase Transition
TFfPhaseTrans::TFfPhaseTrans(const int& NNds, const int& StartNds,  const double& Take2AmbPr,
                             const double& ProbOrphan, const double& ProbIncrement, const int& NRunsPerFB) :
  BurExpFire(false), NNodes(NNds), StartNodes(StartNds), NRuns(NRunsPerFB), Take2AmbProb(Take2AmbPr),
  OrphanProb(ProbOrphan), ProbInc(ProbIncrement), FBPrGSetH(), FBPrGStatH() {
  TakeStatSet = TFSet() | gsEffDiam;
}

PFfPhaseTrans TFfPhaseTrans::New(const int& NNds, const int& StartNds,  const double& Take2AmbPr,
                                 const double& ProbOrphan, const double& ProbIncrement, const int& NRunsPerFB) {
  return new TFfPhaseTrans(NNds, StartNds, Take2AmbPr, ProbOrphan, ProbIncrement, NRunsPerFB);
}

TFfPhaseTrans::TFfPhaseTrans(TSIn& SIn) : BurExpFire(SIn), NNodes(SIn), StartNodes(SIn), NRuns(SIn),
  Take2AmbProb(SIn), OrphanProb(SIn), ProbInc(SIn), FBPrGSetH(SIn), FBPrGStatH(SIn), TakeStatSet(SIn) {
}
PFfPhaseTrans TFfPhaseTrans::Load(TSIn& SIn) {
  return new TFfPhaseTrans(SIn);
}

void TFfPhaseTrans::Save(TFOut& SOut) const {
  BurExpFire.Save(SOut);  NNodes.Save(SOut);
  StartNodes.Save(SOut);  NRuns.Save(SOut);
  Take2AmbProb.Save(SOut);  OrphanProb.Save(SOut);
  ProbInc.Save(SOut);
  FBPrGSetH.Save(SOut);  FBPrGStatH.Save(SOut);
  TakeStatSet.Save(SOut);
}

TStr TFfPhaseTrans::GetTitleStr(const int& ValN) const {
  const PGrowthStat AvgStat = GetAvgGStat(ValN);
  const double AvgDeg = 2.0*AvgStat->Last()->Edges / double(AvgStat->Last()->Nodes);
  const double DiamCf = GetDecDiam(ValN).Val1;
  const TFltPr GplCf = GetGplCf(ValN);
  return TStr::Fmt("FWD: %.4f  BCK: %.4f  DIAM-INC: %.2f  GPL: %.2f (%.2f) AvgDeg:%.1f",
    GetFProb(ValN), GetBProb(ValN), DiamCf, GplCf.Val1(), GplCf.Val2(), AvgDeg);
}

TStr TFfPhaseTrans::GetFNm(const TStr& FNmPref) const {
  return TStr::Fmt("%s.n%dk.s%d", FNmPref.CStr(), int(NNodes/1000.0), StartNodes());
}

TFltPr TFfPhaseTrans::GetGplCf(const int& ValN) const {
  const TGrowthSet& GrowthSet = *GetGSet(ValN);
  TMom GplMom;
  for (int i = 0; i < GrowthSet.Len(); i++) {
    GplMom.Add(GrowthSet[i]->GetGplCf());
  }
  GplMom.Def();
  return TFltPr(GplMom.GetMean(), GplMom.GetSDev());
}

TFltPr TFfPhaseTrans::GetDecDiam(const int& ValN) const {
  const TGrowthSet& GrowthSet = *GetGSet(ValN);
  TMom DiamMom;
  for (int i = 0; i < GrowthSet.Len(); i++) {
    DiamMom.Add(GrowthSet[i]->IsDecEffDiam());
  }
  DiamMom.Def();
  return TFltPr(DiamMom.GetMean(), DiamMom.GetSDev());
}

TFltPr TFfPhaseTrans::GetEffDiam(const int& ValN, const int& AtTick) const {
  const TGrowthSet& GrowthSet = *GetGSet(ValN);
  TMom DiamMom;
  for (int i = 0; i < GrowthSet.Len(); i++) {
    if (AtTick != -1) { DiamMom.Add(GrowthSet[i]->At(AtTick)->EffDiam); }
    else { DiamMom.Add(GrowthSet[i]->Last()->EffDiam); }
  }
  DiamMom.Def();
  return TFltPr(DiamMom.GetMean(), DiamMom.GetSDev());
}

void TFfPhaseTrans::GetFProbV(TFltV& FProbV) const {
  THash<TFlt, TInt> FProbH;
  for (int i = 0; i < Len(); i++) {
    FProbH.AddKey(GetFProb(i)); }
  FProbH.GetKeyV(FProbV);
  FProbV.Sort();
}

TFltPr TFfPhaseTrans::RunForestFire(double FwdProb, double BckProb, const bool& Plot) {
  FwdProb = TMath::Round(FwdProb, 4);
  BckProb = TMath::Round(BckProb, 4);
  printf("\n---FwdBurnProb--%g----Back--%g------------------[%s]\n", FwdProb, BckProb, TExeTm::GetCurTm());
  if (FBPrGStatH.IsKey(TFltPr(FwdProb, BckProb))) {
    printf("*** SKIP -- already have it\n");
    const TGrowthStat& Stat = *FBPrGStatH.GetDat(TFltPr(FwdProb, BckProb));
    return TFltPr(Stat.GetGplCf(), Stat.IsDecEffDiam());
  }
  if (BckProb > 1.0) return TFltPr(-1, -1);
  PGrowthSet GrowthSet = TGrowthSet::New();
  TExeTm ExeTm;
  TFfGGen ForestFire(false, StartNodes, FwdProb, BckProb, 1.0, Take2AmbProb, OrphanProb);
  ForestFire.TakeStat(TakeStatSet); // gsDiam
  GrowthSet->Clr(false);
  for (int run = 0; run < NRuns; run++) {
    printf("\nRUN %d        [last run %s]\n", run+1, ExeTm.GetTmStr());  ExeTm.Tick();
    TFfGGen::TStopReason StopReason =*//* ForestFire.GenGraph(NNodes, true);
    if (! ForestFire.GetGrowthStat()->Empty()) {
      GrowthSet->Add(ForestFire.GetGrowthStat()); }
  }
  IAssert(! GrowthSet.Empty());
  // add stat
  FBPrGSetH.AddDat(TFltPr(FwdProb, BckProb), GrowthSet);
  PGrowthStat AvgStat = TGrowthStat::New();
  AvgStat->AvgGrowthStat(*GrowthSet);
  FBPrGStatH.AddDat(TFltPr(FwdProb, BckProb), AvgStat);
  const double DiamCf = LastDecDiam().Val1;
  const double GplCf = LastGplCf().Val1;
  // plot
  if (Plot && ! AvgStat.Empty()) {
    const TStr FNm = TStr::Fmt("F%04d.B%04d", int(FwdProb*10000), int(BckProb*10000));
    const TStr Title = GetTitleStr(Len()-1);
    AvgStat->PlotDiam(FNm, Title);
    AvgStat->PlotGpl(FNm, Title, true, false, false, false, false);
  }
  return TFltPr(GplCf, DiamCf);
}

void TFfPhaseTrans::FwdProbSteps(const double& MinFwdProb, const double& MaxFwdProb, const double& BckProb) {
  const TStr BinFNm = TFile::GetUniqueFNm(TStr::Fmt("phaseFwd.n%dk.s%d_#.bin", int(NNodes/1000.0), StartNodes()));
  TFfGGen::TimeLimitSec = 10*60;
  for (double FwdProb = MinFwdProb; FwdProb <= MaxFwdProb; FwdProb += ProbInc) {
    RunForestFire(FwdProb, BckProb, true);
    { TFOut FOut(BinFNm);
    Save(FOut); }
  }
}

void TFfPhaseTrans::FwdProbStepsFact(const double& MinFwdProb, const double& MaxFwdProb, const double& BckFact) {
  const TStr BinFNm = TFile::GetUniqueFNm(TStr::Fmt("phaseFwd.n%dk.s%d_#.bin", int(NNodes/1000.0), StartNodes()));
  TFfGGen::TimeLimitSec = 10*60;
  for (double FwdProb = MinFwdProb; FwdProb <= MaxFwdProb; FwdProb += ProbInc) {
    RunForestFire(FwdProb, BckFact*FwdProb, true);
    { TFOut FOut(BinFNm);
    Save(FOut); }
  }
}

void TFfPhaseTrans::FwdBckPhasePlot(const double& MinFwdProb, const double& MaxFwdProb, const double& MinBckFact,
                                    const double& MaxBckFact, const int& TimeLimitSec) {
  const TStr BinFNm = TFile::GetUniqueFNm(TStr::Fmt("phaseFF.n%dk.s%d_#.bin", int(NNodes/1000.0), StartNodes()));
  TFfGGen::TimeLimitSec = TimeLimitSec;
  for (double FwdProb = MinFwdProb; FwdProb <= MaxFwdProb; FwdProb += ProbInc) {
    for (double BckFact = MinBckFact; BckFact < MaxBckFact+0.001; BckFact += ProbInc) {
      const double BckProb = FwdProb * BckFact;
      RunForestFire(FwdProb, BckProb, true);
      { TFOut FOut(BinFNm);
      Save(FOut); }
    }
  }
}

void TFfPhaseTrans::FindGplPhaseTr(const double& StartFProb, const double& FollowGplCf, const TStr& FNmPref, const TStr& Desc) {
  const TStr FNm = TStr::Fmt("phGPL.%s", GetFNm(FNmPref).CStr());
  const double Tolerance = 0.01;
  const double MinDelta = 0.001;
  const bool Plot = false;
  TFfGGen::TimeLimitSec = 10*60;
  TGrowthStat::MinNodesEdges = 2*(StartNodes-1)+100;
  const int OldNDiamRuns = TGraphStat::NDiamRuns;
  TGraphStat::NDiamRuns = 1;  //!!! diameter
  TakeStat(TFSet() | gsEffDiam);
  FILE *F = fopen((FNm+".txt").CStr(), "wt");
  fprintf(F, "FollowGplCf:  %g\n", FollowGplCf);
  fprintf(F, "StartNodes:   %d\n", StartNodes());
  fprintf(F, "Take2AmbProb: %g\n", Take2AmbProb());
  fprintf(F, "OrphanProb:   %g\n", OrphanProb());
  fprintf(F, "Tolerance:    %g\n", Tolerance);
  double FwdProb = StartFProb, LBckRat=0, RBckRat=1, BckRat, GplCf;
  //TFltPr GplPr;
  while (FwdProb < 1.0) {
    while (true) {
      BckRat = (RBckRat+LBckRat) / 2;
      fprintf(F, "FWD: %g, (%f -- %f)", FwdProb, LBckRat, RBckRat);
      GplCf = RunForestFire(FwdProb, FwdProb*BckRat, Plot).Val1;
      IAssert(GplCf != -1);
      fprintf(F, "  %f  gpl: %.4f (%.4f)", BckRat, GplCf, LastGplCf().Val2());
      if (TMath::IsInEps(GplCf - FollowGplCf, Tolerance)) { fprintf(F, "  ***\n"); break; }
      if (RBckRat-LBckRat < MinDelta) { fprintf(F, "  gap\n"); break; }
      if (GplCf > FollowGplCf) { RBckRat = BckRat; } else { LBckRat = BckRat; }
      fprintf(F, "\n");  fflush(F);
    }
    FwdProb += ProbInc;
    RBckRat = BckRat+0.01;
    if (RBckRat > 1.0) RBckRat = 1.0;
    LBckRat = RBckRat - 0.2;
    if (LBckRat < 0.0) LBckRat = 0.0;
    { TFOut FOut(FNm+".bin");
     Save(FOut); }
    SaveGplPhaseTr(FollowGplCf, FNmPref, Desc);
    fprintf(F, "\n");
  }
  fclose(F);
  TGraphStat::NDiamRuns = OldNDiamRuns;
}

void TFfPhaseTrans::SaveGplPhaseTr(const double& FollowGplCf, const TStr& FNmPref, const TStr& Desc) {
  const double Tolerance = 0.02;
  THash<TFlt,  TIntFltPr> FProbH;
  for (int i = 0; i < Len(); i ++) {
    const double FProb = GetFProb(i);
    const double GplCf = GetGplCf(i).Val1;
    if (TMath::IsInEps(GplCf-FollowGplCf, Tolerance)) {
      if (! FProbH.IsKey(FProb)) {
        FProbH.AddDat(FProb, TIntFltPr(i, GplCf)); }
      else {
        const double bestCf = FProbH.GetDat(FProb).Val2;
        if (fabs(bestCf - FollowGplCf) > fabs(GplCf - FollowGplCf)) {
          FProbH.AddDat(FProb, TIntFltPr(i, GplCf)); }
      }
    }
  }
  TVec<TPair<TFlt, TIntFltPr> > FProbV;
  FProbH.GetKeyDatPrV(FProbV);  FProbV.Sort();
  const bool HasDiam = TakeStatSet.In(gsEffDiam);
  FILE *F = fopen(TStr::Fmt("phGPL.%s.tab", GetFNm(FNmPref).CStr()).CStr(), "wt");
  if (! Desc.Empty()) fprintf(F, "%s\n", Desc.CStr());
  fprintf(F, "FollowGplCf:  %g\n", FollowGplCf);
  fprintf(F, "StartNodes:   %d\n", StartNodes());
  fprintf(F, "Take2AmbProb: %g\n", Take2AmbProb());
  fprintf(F, "OrphanProb:   %g\n", OrphanProb());
  fprintf(F, "Tolerance:    %g\n", Tolerance);
  fprintf(F, "id\tFProb\tBProb\tBRatio\tGlp\tGlpDev");
  if (HasDiam) {  fprintf(F, "\tDiamCf\tDiamDev\tEffDiam"); }
  fprintf(F, "\n");
  for (int i = 0; i < FProbH.Len(); i++) {
    const int Id = FProbV[i].Val2.Val1;
    const TFltPr Gpl = GetGplCf(Id);
    fprintf(F, "%d\t%f\t%f\t%f\t", Id, GetFProb(Id), GetBProb(Id), GetBProb(Id)/GetFProb(Id));
    fprintf(F, "%f\t%f", Gpl.Val1(), Gpl.Val2());
    if (HasDiam) {
      const TFltPr DiamCf = GetDecDiam(Id);
      fprintf(F, "\t%f\t%f\t%f", DiamCf.Val1(), DiamCf.Val2(), GetEffDiam(Id, -1).Val1());
    }
    fprintf(F, "\n");
  }
  fclose(F);
}

void TFfPhaseTrans::FindDiamPhaseTr(const double& StartFProb, const double& FollowDiamCf, const TStr& FNmPref, const TStr& Desc) {
  const TStr FNm = TStr::Fmt("phDIAM.%s", GetFNm(FNmPref).CStr());
  const double Tolerance = 0.01;
  const double MinDelta = 0.001;
  const bool Plot = false;
  TFfGGen::TimeLimitSec = 10*60;
  const int OldNDiamRuns = TGraphStat::NDiamRuns;
  TGraphStat::NDiamRuns = 1;
  TGrowthStat::MinNodesEdges = 2*(StartNodes-1)+100;
  TakeStat(TFSet() | gsEffDiam);
  FILE *F = fopen((FNm+".txt").CStr(), "wt");
  fprintf(F, "FollowDiamCf: %g\n", FollowDiamCf);
  fprintf(F, "StartNodes:   %d\n", StartNodes());
  fprintf(F, "Take2AmbProb: %g\n", Take2AmbProb());
  fprintf(F, "OrphanProb:   %g\n", OrphanProb());
  fprintf(F, "Tolerance:    %g\n", Tolerance);
  double FwdProb = StartFProb, LBckRat=0, RBckRat=1, BckRat, DiamCf;
  //TFltPr GplPr;
  while (FwdProb < 1.0) {
    while (true) {
      BckRat = (RBckRat+LBckRat) / 2;
      fprintf(F, "FWD: %g, (%f -- %f)", FwdProb, LBckRat, RBckRat);
      DiamCf = RunForestFire(FwdProb, FwdProb*BckRat, Plot).Val2;
      IAssert(DiamCf != -1);
      fprintf(F, "  %f  diam: %.4f (%.4f)", BckRat, DiamCf, LastDecDiam().Val2());
      if (TMath::IsInEps(DiamCf - FollowDiamCf, Tolerance)) { fprintf(F, "  ***\n"); break; }
      if (RBckRat-LBckRat < MinDelta) { fprintf(F, "  gap\n"); break; }
      if (DiamCf < FollowDiamCf) { RBckRat = BckRat; } else { LBckRat = BckRat; }
      fprintf(F, "\n");  fflush(F);
    }
    FwdProb += ProbInc;
    RBckRat = BckRat+0.05;
    if (RBckRat > 1.0) RBckRat = 1.0;
    LBckRat = RBckRat - 0.15;
    if (LBckRat < 0.0) LBckRat = 0.0;
    { TFOut FOut(FNm+".bin");
    Save(FOut); }
    SaveDiamPhaseTr(FollowDiamCf, FNmPref, Desc);
    fprintf(F, "\n");
  }
  fclose(F);
  TGraphStat::NDiamRuns = OldNDiamRuns;
}

void TFfPhaseTrans::SaveDiamPhaseTr(const double& FollowDiamCf, const TStr& FNmPref, const TStr& Desc) {
  const double Tolerance = 0.03;
  THash<TFlt,  TIntFltPr> FProbH;
  for (int i = 0; i < Len(); i ++) {
    const double FProb = GetFProb(i);
    const double DiamCf = GetDecDiam(i).Val1;
    if (TMath::IsInEps(DiamCf - FollowDiamCf, Tolerance)) {
      if (! FProbH.IsKey(FProb)) {
        FProbH.AddDat(FProb, TIntFltPr(i, DiamCf)); }
      else {
        const double bestCf = FProbH.GetDat(FProb).Val2;
        if (fabs(bestCf - FollowDiamCf) > fabs(DiamCf - FollowDiamCf)) {
          FProbH.AddDat(FProb, TIntFltPr(i, DiamCf)); }
      }
    }
  }
  TVec<TPair<TFlt, TIntFltPr> > FProbV;
  FProbH.GetKeyDatPrV(FProbV);  FProbV.Sort();
  FILE *F = fopen(TStr::Fmt("phDIAM.%s.tab", GetFNm(FNmPref).CStr()).CStr(), "wt");
  if (! Desc.Empty()) fprintf(F, "%s\n", Desc.CStr());
  fprintf(F, "FollowDiamCf: %g\n", FollowDiamCf);
  fprintf(F, "StartNodes:   %d\n", StartNodes());
  fprintf(F, "Take2AmbProb: %g\n", Take2AmbProb());
  fprintf(F, "OrphanProb:   %g\n", OrphanProb());
  fprintf(F, "Tolerance:    %g\n", Tolerance);
  fprintf(F, "id\tFProb\tBProb\tBRatio\tDiamCf\tDiamDev\tGplCf\tGplDev\tEffDiam\n");
  for (int i = 0; i < FProbV.Len(); i++) {
    const int Id = FProbV[i].Val2.Val1;
    const TFltPr DiamCf = GetDecDiam(Id);
    const TFltPr GplCf = GetGplCf(Id);
    const TFltPr EffDiam = GetEffDiam(Id, -1);
    fprintf(F, "%d\t%f\t%f\t%f\t", Id, GetFProb(Id), GetBProb(Id), GetBProb(Id)/GetFProb(Id));
    fprintf(F, "%f\t%f\t%f\t%f\t%f\n", DiamCf.Val1(), DiamCf.Val2(), GplCf.Val1(), GplCf.Val2(), EffDiam.Val1());
  }
  fclose(F);
}

void TFfPhaseTrans::Merge(const PFfPhaseTrans& FfPhaseTrans) {
  Merge(*FfPhaseTrans);
}

void TFfPhaseTrans::Merge(const TFfPhaseTrans& FfPhaseTrans) {
  printf("Merging:\n");
  printf("  source      %6d  (Fwd,Bck) pairs\n", FfPhaseTrans.Len());
  printf("  destination %6d  (Fwd,Bck) pairs\n", Len());
  IAssert(BurExpFire == FfPhaseTrans.BurExpFire);
  IAssert(NNodes == FfPhaseTrans.NNodes);
  IAssert(StartNodes == FfPhaseTrans.StartNodes);
  IAssert(Take2AmbProb == FfPhaseTrans.Take2AmbProb);
  IAssert(FBPrGSetH.Len() == FBPrGStatH.Len());
  IAssert(FfPhaseTrans.FBPrGSetH.Len() == FfPhaseTrans.FBPrGStatH.Len());
  for (int i1 = 0; i1 < FfPhaseTrans.FBPrGSetH.Len(); i1++) {
    IAssert(FfPhaseTrans.FBPrGSetH.GetKey(i1) == FfPhaseTrans.FBPrGStatH.GetKey(i1));
    const TFltPr& Key = FfPhaseTrans.FBPrGSetH.GetKey(i1);
    if (! FBPrGStatH.IsKey(Key)) {
      const PGrowthStat Stat = FfPhaseTrans.FBPrGStatH[i1];
      const PGrowthSet Set = FfPhaseTrans.FBPrGSetH[i1];
      FBPrGStatH.AddDat(Key, Stat);
      FBPrGSetH.AddDat(Key, Set);
    }
  }
  printf("  ** merged   %6d  (Fwd,Bck) pairs\n", Len());
}
//*/

/////////////////////////////////////////////////
// Undirected Forest Fire (does not densify!)

// Node selects N~geometric(1.0-BurnProb)-1 links and burns them. 
// geometirc(p) has mean 1/(p), so for given BurnProb, we burn 1/(1-BurnProb) links in average
int TUndirFFire::BurnGeoFire(const int& StartNId) {
  BurnedSet.Clr(false);
  BurningNIdV.Clr(false);  
  NewBurnedNIdV.Clr(false);
  AliveNIdV.Clr(false);
  const TUNGraph& G = *Graph;
  int NBurned = 1;
  BurnedSet.AddKey(StartNId);
  BurningNIdV.Add(StartNId);
  while (! BurningNIdV.Empty()) {
    for (int node = 0; node < BurningNIdV.Len(); node++) {
      const int& BurningNId = BurningNIdV[node];
      const TUNGraph::TNodeI& Node = G.GetNI(BurningNId);
      // find unburned links
      AliveNIdV.Clr(false); // unburned links
      for (int e = 0; e < Node.GetOutDeg(); e++) {
        const int OutNId = Node.GetOutNId(e);
        if (! BurnedSet.IsKey(OutNId)) {
          AliveNIdV.Add(OutNId); }
      }
      // number of links to burn (geometric coin). Can also burn 0 links
      const int BurnNLinks = Rnd.GetGeoDev(1.0-BurnProb) - 1;
      if (! AliveNIdV.Empty() && BurnNLinks > 0) {
        AliveNIdV.Shuffle(Rnd);
        for (int i = 0; i < TMath::Mn(BurnNLinks, AliveNIdV.Len()); i++) {
          BurnedSet.AddKey(AliveNIdV[i]);
          NewBurnedNIdV.Add(AliveNIdV[i]);
          NBurned++;
        }
      }
    }
    BurningNIdV.Swap(NewBurnedNIdV);   // each node is burning just 1 time step
    // BurningNIdV.AddV(NewBurnedNIdV);   // all nodes are burning eternally
    NewBurnedNIdV.Clr(false);
  }
  IAssert(BurnedSet.Len() == NBurned);
  return NBurned;
}

TFfGGen::TStopReason TUndirFFire::AddNodes(const int& GraphNodes, const bool& FloodStop) {
  printf("\n***Undirected GEO ForestFire: graph(%d,%d) add %d nodes, burn prob %.3f\n", 
    Graph->GetNodes(), Graph->GetEdges(), GraphNodes, BurnProb);
  TExeTm ExeTm;
  int Burned1=0, Burned2=0, Burned3=0; // last 3 fire sizes
  TIntPrV NodesEdgesV;
  // create initial set of nodes
  if (Graph.Empty()) { Graph = PUNGraph::New(); }
  if (Graph->GetNodes() == 0) { Graph->AddNode(); }
  int NEdges = Graph->GetEdges();
  // forest fire
  for (int NNodes = Graph->GetNodes()+1; NNodes <= GraphNodes; NNodes++) {
    const int NewNId = Graph->AddNode(-1);
    IAssert(NewNId == Graph->GetNodes()-1); // node ids have to be 0...N
    const int StartNId = Rnd.GetUniDevInt(NewNId);
    const int NBurned = BurnGeoFire(StartNId);
    // add edges to burned nodes
    for (int e = 0; e < NBurned; e++) {
      Graph->AddEdge(NewNId, GetBurnedNId(e)); }
    NEdges += NBurned;
    Burned1=Burned2;  Burned2=Burned3;  Burned3=NBurned;
    if (NNodes % Kilo(1) == 0) {
      printf("(%d, %d)    burned: [%d,%d,%d]  [%s]\n", NNodes, NEdges, Burned1, Burned2, Burned3, ExeTm.GetStr()); 
      NodesEdgesV.Add(TIntPr(NNodes, NEdges));
    }
    if (FloodStop && NEdges>1000 && NEdges/double(NNodes)>100.0) { // average node degree is more than 50
      printf("!!! FLOOD. G(%6d, %6d)\n", NNodes, NEdges);  return TFfGGen::srFlood; }
  }
  printf("\n");
  IAssert(Graph->GetEdges() == NEdges);
  return TFfGGen::srOk;
}
