TTree: creating a sub-branch of a branch from scratch


_ROOT Version:6.xx
_Platform: linux / CentOs7
Compiler: g++


I need to create sub-branches of branches in a TTree, that will contain basic leaves (not from a custom class)

The TBranch documentation mentions the following TBranch constructor:
TBranch (TBranch* parent, const char* name, void* address, const char* leaflist, Int_t basketsize = 32000, Int_t compress = -1)
But this does not seem to work…

I have seen some questions about that in the forum, but no answer was fitting my need.
I wrote a very basic example (I can provide the full test code if needed):

  // define a branch
  TBranch * myBranch = outTree->Branch ( "MainBr", &myBuffer[0], "VarA/I:VarB/I:VarC/I" );

  // define a sub-branch of the main branch
  TBranch * mySubBranch = new TBranch ( myBranch, "SubBr", &mySubBuffer[0], "VarSub1/I:VarSub2/I" );

  // this seems to be need...
  myBranch->GetListOfBranches()->Add ( mySubBranch );

Then, when looking recursively to the branches in the TTree, the main branch and the sub-branch exists:

  • after creation in a ROOT file
  • when reading the TTree from the file
  • ** but not in the ROOT TBrowser !**

In addition, when filling entries with test values, the leaves of the main branch are OK, but the leaves fo the sub-branches are 0 !

So I guess I missed something to create a simple branch within a branch…
Or maybe this is not possible at all, but then I wonder why this TBranch constructor is designed for.
Thanks for any help.

What is the goal you are trying to achieve? (there may or may not be a better way to achieve that goal … as you noticed hand crafting of branch hierarchy is not well tested/supported).

Cheers,
Philippe.

My goal is to be able to group a large set of parameters into branches and sub-branches, instead of a very long list of leaves.

The data may come from different sources, that’s why I cannot create an predefined object to store in the TTree.
I really would like to be able to create simple leaves at various/any branch/sub-branch level.

Does it make it more clear ?

The branch object created from:

TBranch * myBranch = outTree->Branch ( "MainBr", &myBuffer[0], "VarA/I:VarB/I:VarC/I" );

assumes there is no sub-branches (i.e. there is no code to recurse).
You could try:

  // define a top level branch.
  TBranch *toplevel = new TBranchElement()
  toplevel->SetName("MainBr.");
  outTree->GetListOfBranches()->Add(toplevel);

  // define a  sub-branch of the main branch
  TBranch * mySubBranch1 = new TBranch ( outTree, &myBuffer[0], "VarA/I:VarB/I:VarC/I" );

  // define a sub-branch of the main branch
  TBranch * mySubBranch2 = new TBranch ( outTree, "SubBr", &mySubBuffer[0], "VarSub1/I:VarSub2/I" );

  // this seems to be need...
  toplevel->GetListOfBranches()->Add ( mySubBranch1 );
  toplevel->GetListOfBranches()->Add ( mySubBranch2 );

Cheers,
Philippe.

Hi,
I can create the TTree structure as you mentioned.
But when writing an entry (with Fill(), the code crashes…

#0 0x00007f97a486d46c in waitpid () from /usr/lib64/libc.so.6
#1 0x00007f97a47eaf62 in do_system () from /usr/lib64/libc.so.6
#2 0x00007f97a8500219 in TUnixSystem::StackTrace (this=0x16608e0) at
/root/Téléchargements/root-6.08.06/core/unix/src/TUnixSystem.cxx:2405
#3 0x00007f97a8502a2c in TUnixSystem::DispatchSignals (this=0x16608e0,
sig=kSigSegmentationViolation) at
/root/Téléchargements/root-6.08.06/core/unix/src/TUnixSystem.cxx:3625
#4
#5 0x00007f97a6a8a187 in TBranchElement::Fill (this=0x2791440) at
/root/Téléchargements/root-6.08.06/tree/tree/src/TBranchElement.cxx:1213
#6 0x00007f97a6ac3df3 in TTree::Fill (this=0x2629220) at
/root/Téléchargements/root-6.08.06/tree/tree/src/TTree.cxx:4393
#7 0x000000000040187d in main ()

If I do not put any entry, and write it to file, opening it in ROOT
TBrowser (to check) also results in a crash…

here is the code (TestTree.cxx):
compiled with: g++ TestTree.cxx -o TestTree root-config --cflags --libs

thanks for your help !
Jérôme

Adding/using this should help:

  TBranch *toplevel = new TBranchElement()
  toplevel->SetName("MainBr.");
  toplevel->SetTree( outTree );

Indeed, I could fill entries.
But it crashes when trying to read it, either from a program or from a
ROOT TBrowser (even before getting any entry).

what are the new stack traces?

hi,
sorry for the delay…
the stack traces ares the following

#0 0x00007fdd9433846c in waitpid () from /usr/lib64/libc.so.6
#1 0x00007fdd942b5f62 in do_system () from /usr/lib64/libc.so.6
#2 0x00007fdd97fcb219 in TUnixSystem::StackTrace (this=0x9fe8e0) at
/root/Téléchargements/root-6.08.06/core/unix/src/TUnixSystem.cxx:2405
#3 0x00007fdd97fcda2c in TUnixSystem::DispatchSignals (this=0x9fe8e0,
sig=kSigSegmentationViolation) at
/root/Téléchargements/root-6.08.06/core/unix/src/TUnixSystem.cxx:3625
#4
#5 0x00007fdd9654c276 in TBranchElement::Streamer (this=0x1d1f3b0,
R(bool)=…) at
/root/Téléchargements/root-6.08.06/tree/tree/src/TBranchElement.cxx:5349
#6 0x00007fdd9793083c in Streamer (onfile_class=0x0, b=…,
obj=0x1d1f3b0, this=0x1e89bc0) at
/usr/local/src/buildroot/include/root/TClass.h:547
#7 TBufferFile::ReadObjectAny (this=this
entry=0x1d25a00, clCast=0x19af370) at
/root/Téléchargements/root-6.08.06/io/io/src/TBufferFile.cxx:2506
#8 0x00007fdd97f57f50 in TObjArray::Streamer (this=0x1d26270, b=…) at
/root/Téléchargements/root-6.08.06/core/cont/src/TObjArray.cxx:445
#9 0x00007fdd97a8965c in TStreamerInfo::ReadBuffer<char
*>
(this=0x1e15c90, b=…, arr=
0x7fff8c947ec8: 0x7fff8c947ec0, compinfo=0x1e88078, first=first
entry=0, last=last
entry=1, narr=narr
entry=1, eoffset=0, arrayMode=0, arrayMode
entry=2) at
/root/Téléchargements/root-6.08.06/io/io/src/TStreamerInfoReadBuffer.cxx:1306
#10 0x00007fdd9799a65d in TStreamerInfoActions::GenericReadAction
(buf=…, addr=, config=) at
/root/Téléchargements/root-6.08.06/io/io/src/TStreamerInfoActions.cxx:167
#11 0x00007fdd9792dcf5 in operator() (this=0x1e85270, this=0x1e85270,
object=0x1d26120, buffer=…) at
/usr/local/src/buildroot/include/root/TStreamerInfoActions.h:116
#12 TBufferFile::ApplySequence (this=0x1d25a00, sequence=…,
obj=0x1d26120) at
/root/Téléchargements/root-6.08.06/io/io/src/TBufferFile.cxx:3982
#13 0x00007fdd9792c620 in TBufferFile::ReadClassBuffer (this=0x1d25a00,
cl=0x1d2caa0, pointer=pointer
entry=0x1d26120, version=version
entry=19, start=start
entry=46, count=count
entry=1759, onFileClass=onFileClass
entry=0x0) at
/root/Téléchargements/root-6.08.06/io/io/src/TBufferFile.cxx:3761
#14 0x00007fdd96591d84 in TTree::Streamer (this=0x1d26120, b=…) at
/root/Téléchargements/root-6.08.06/tree/tree/src/TTree.cxx:8796
#15 0x00007fdd9797abd5 in TKey::ReadObj (this=0x1d93f10) at
/root/Téléchargements/root-6.08.06/io/io/src/TKey.cxx:813
#16 0x00007fdd97944062 in TDirectoryFile::Get (this=0xa85840,
namecycle=) at
/root/Téléchargements/root-6.08.06/io/io/src/TDirectoryFile.cxx:930
#17 0x000000000040197a in main ()

It should be away if we also add a dummy TLeaf to the artificial toplevel branch:

          TLeaf* leaf = new TLeafElement(toplevel, "", 0, 0);
          toplevel->GetListOfLeaves()->Add(leaf);

Cheers,
Philippe.

It works like that !
Thank you very much.

I will explore a bit more by increasing slightly the complexity of the
tree structure.
(I also wonder if the dummy TLeaf could also be a real one… I will
test that.

One remark: I initially called the top branch “MainBr.” (with a dot, as
suggested).
But it seems to work exactly the same without the dot.

So I report here the working code.

  • TFile * outFile = new TFile ( “Trees/TreeFile.root”, “RECREATE” );**
    ** TTree * outTree = new TTree ( “MyTree”, “MyTree” );**

Hi again.
Your solution allows to create the TTree and read it, nevertheless, it
crashes when trying to draw a leaf.

For example, in a TBrowser, when double-clicking on a leaf, I get the
following crash:

*#0  0x00007fbee238446c in waitpid () from /usr/lib64/libc.so.6
#1  0x00007fbee2301f62 in do_system () from /usr/lib64/libc.so.6
#2  0x00007fbee338a699 in TUnixSystem::StackTrace (this=0xaa08e0) at 
/usr/local/src/root-6.08.06/core/unix/src/TUnixSystem.cxx:2405
#3  0x00007fbee338cf6c in TUnixSystem::DispatchSignals (this=0xaa08e0, 
sig=kSigSegmentationViolation) at 
/usr/local/src/root-6.08.06/core/unix/src/TUnixSystem.cxx:3625
#4  <signal handler called>
#5  TBranchElement::FindBranch (this=0x56af350, name=0x7fff51df1430 
"Sub1.") at 
/usr/local/src/root-6.08.06/tree/tree/src/TBranchElement.cxx:1754
#6  0x00007fbecd5b55a2 in TTree::FindBranch (this=0x5686de0, 
branchname=0x7fff51df1430 "Sub1.") at 
/usr/local/src/root-6.08.06/tree/tree/src/TTree.cxx:4580
#7  0x00007fbecd8be03a in TTreeFormula::FindLeafForExpression (this=this
entry=0x57101c0, expression=expression
entry=0x7fff51df3190 "Sub1.VarA1", leaf=
0x7fff51df30e8: 0x0, leftover=..., final=
0x7fff51df30d9: false, paran_level=
0x7fff51df30dc: 0, castqueue=..., aliasUsed=std::vector of length 0, 
capacity 0, useLeafCollectionObject=
0x7fff51df30da: false, fullExpression=0x7fff51df4069 "Sub1.VarA1") at 
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreeFormula.cxx:2387
#8  0x00007fbecd8bfe37 in TTreeFormula::DefinedVariable (this=0x57101c0, 
name=..., action=<optimized out>) at 
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreeFormula.cxx:2825
#9  0x00007fbecc61c8b3 in ROOT::v5::TFormula::Analyze (this=0x57101c0, 
schain=0x7fff51df4489 "Sub1.VarA1", err=
0x7fff51df442c: 0, offset=0) at 
/usr/local/src/root-6.08.06/hist/hist/src/TFormula_v5.cxx:1380
#10 0x00007fbecc6260a4 in ROOT::v5::TFormula::Compile (this=0x57101c0, 
expression=<optimized out>) at 
/usr/local/src/root-6.08.06/hist/hist/src/TFormula_v5.cxx:2367
#11 0x00007fbecd8b705b in TTreeFormula::Init (this=0x57101c0, 
name=0x56e0ee0 "Var1", expression=0x56c3b59 "Sub1.VarA1") at 
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreeFormula.cxx:209
#12 0x00007fbecd8b75cd in TTreeFormula::TTreeFormula (this=0x57101c0, 
name=0x56e0ee0 "Var1", expression=0x56c3b59 "Sub1.VarA1", 
tree=<optimized out>) at 
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreeFormula.cxx:162
#13 0x00007fbecd89ee7d in TSelectorDraw::CompileVariables 
(this=0x56f34f0, varexp=<optimized out>, selection=<optimized out>) at 
/usr/local/src/root-6.08.06/tree/treeplayer/src/TSelectorDraw.cxx:1008
#14 0x00007fbecd8a1ce3 in TSelectorDraw::Begin (this=0x56f34f0, 
tree=<optimized out>) at 
/usr/local/src/root-6.08.06/tree/treeplayer/src/TSelectorDraw.cxx:477
#15 0x00007fbecd8c9e94 in TTreePlayer::Process (this=0x56ba000, 
selector=0x56f34f0, option=0x4a1bb99 "", nentries=2, firstentry=0) at 
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreePlayer.cxx:2231
#16 0x00007fbecd8ca7cf in TTreePlayer::DrawSelect (this=0x56ba000, 
varexp0=<optimized out>, selection=0x7fbecd5cf555 "", option=0x4a1bb99 
"", nentries=1000000000000, firstentry=0) at 
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreePlayer.cxx:414
#17 0x00007fbecd5a3eef in TLeaf::Browse (this=<optimized out>, 
b=0x7fbee39270e0) at /usr/local/src/root-6.08.06/tree/tree/src/TLeaf.cxx:135
#18 0x00007fbecdd6193d in TGFileBrowser::DoubleClicked (this=0x4ae2310, 
item=<optimized out>) at 
/usr/local/src/root-6.08.06/gui/gui/src/TGFileBrowser.cxx:1332
#19 0x00007fbecefc0046 in ?? ()
#20 0x0000000000000000 in ?? ()*

If you have any idea…
thanks.
Jérôme

Try to replace

TLeaf* leaf = new TLeafElement(toplevel, “”, 0, 0);

with

TLeaf* leaf = new TLeafElement(toplevel, “”, -1, 0);

same crash…

*#0 0x00007fc12da7246c in waitpid () from /usr/lib64/libc.so.6
#1 0x00007fc12d9eff62 in do_system () from /usr/lib64/libc.so.6
#2 0x00007fc12ea78699 in TUnixSystem::StackTrace (this=0x21908e0) at
/usr/local/src/root-6.08.06/core/unix/src/TUnixSystem.cxx:2405
#3 0x00007fc12ea7af6c in TUnixSystem::DispatchSignals (this=0x21908e0,
sig=kSigSegmentationViolation) at
/usr/local/src/root-6.08.06/core/unix/src/TUnixSystem.cxx:3625
#4
#5 TBranchElement::FindBranch (this=0x6d74e10, name=0x7ffc227b2900
“Sub1.”) at
/usr/local/src/root-6.08.06/tree/tree/src/TBranchElement.cxx:1754
#6 0x00007fc118ca35a2 in TTree::FindBranch (this=0x6d923e0,
branchname=0x7ffc227b2900 “Sub1.”) at
/usr/local/src/root-6.08.06/tree/tree/src/TTree.cxx:4580
#7 0x00007fc118fac03a in TTreeFormula::FindLeafForExpression (this=this
entry=0x6dfad40, expression=expression
entry=0x7ffc227b4660 “Sub1.VarA1”, leaf=
0x7ffc227b45b8: 0x0, leftover=…, final=
0x7ffc227b45a9: false, paran_level=
0x7ffc227b45ac: 0, castqueue=…, aliasUsed=std::vector of length 0,
capacity 0, useLeafCollectionObject=
0x7ffc227b45aa: false, fullExpression=0x7ffc227b5539 “Sub1.VarA1”) at
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreeFormula.cxx:2387
#8 0x00007fc118fade37 in TTreeFormula::DefinedVariable (this=0x6dfad40,
name=…, action=) at
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreeFormula.cxx:2825
#9 0x00007fc117d0a8b3 in ROOT::v5::TFormula::Analyze (this=0x6dfad40,
schain=0x7ffc227b5959 “Sub1.VarA1”, err=
0x7ffc227b58fc: 0, offset=0) at
/usr/local/src/root-6.08.06/hist/hist/src/TFormula_v5.cxx:1380
#10 0x00007fc117d140a4 in ROOT::v5::TFormula::Compile (this=0x6dfad40,
expression=) at
/usr/local/src/root-6.08.06/hist/hist/src/TFormula_v5.cxx:2367
#11 0x00007fc118fa505b in TTreeFormula::Init (this=0x6dfad40,
name=0x6ddfe90 “Var1”, expression=0x6db78a9 “Sub1.VarA1”) at
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreeFormula.cxx:209
#12 0x00007fc118fa55cd in TTreeFormula::TTreeFormula (this=0x6dfad40,
name=0x6ddfe90 “Var1”, expression=0x6db78a9 “Sub1.VarA1”,
tree=) at
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreeFormula.cxx:162
#13 0x00007fc118f8ce7d in TSelectorDraw::CompileVariables
(this=0x6df1080, varexp=, selection=) at
/usr/local/src/root-6.08.06/tree/treeplayer/src/TSelectorDraw.cxx:1008
#14 0x00007fc118f8fce3 in TSelectorDraw::Begin (this=0x6df1080,
tree=) at
/usr/local/src/root-6.08.06/tree/treeplayer/src/TSelectorDraw.cxx:477
#15 0x00007fc118fb7e94 in TTreePlayer::Process (this=0x6de8dd0,
selector=0x6df1080, option=0x612e879 “”, nentries=2, firstentry=0) at
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreePlayer.cxx:2231
#16 0x00007fc118fb87cf in TTreePlayer::DrawSelect (this=0x6de8dd0,
varexp0=, selection=0x7fc118cbd555 “”, option=0x612e879
“”, nentries=1000000000000, firstentry=0) at
/usr/local/src/root-6.08.06/tree/treeplayer/src/TTreePlayer.cxx:414
#17 0x00007fc118c91eef in TLeaf::Browse (this=,
b=0x7fc12f0150e0) at /usr/local/src/root-6.08.06/tree/tree/src/TLeaf.cxx:135
#18 0x00007fc11944f93d in TGFileBrowser::DoubleClicked (this=0x61c3820,
item=) at
/usr/local/src/root-6.08.06/gui/gui/src/TGFileBrowser.cxx:1332
#19 0x00007fc11a6b4046 in ?? ()
#20 0x0000000000000000 in ?? ()

We can’t quite get it with the current code. The following hack should help:

struct TBranchElementModifier : public TBranchElement {
   void SetID(int id) { fID = id; }
};

and usage:

((TBranchElementModifier*)toplevel)->SetID(-1);

In addition, for it to work, it is needed to set an address to the
branch, otherwise the sub-branches are not filled, with the message:
Error in TBranchElement::Fill: attempt to fill branch MainBr while
addresss is not set

It seems that the address itself does not matter: I tried the address of
the first sub-branch and the pointer of the branch itself, with the same
result.

But anyway:

  • the TTree could be created with sub-branches of the main branch;
  • I could fill some entries and save the TTree;
  • I could read the TTree in a program or plot the projection of leaves
    in a TBrowser.

So basically, it seems to be fine as follows (1 main branch with 2
sub-branches):
(I would be interesting to give a basic explaination of the main
aspects: need for the SetId, need for the dummy leaf and need for the
address…)

// needed to correct the behavior of the main branch *struct 
TBranchElementModifier : public TBranchElement {****void SetID(int id) { 
fID = id; }****};* 
//-------------------------------------------------------------- int 
main ( int argc, char *argv[] ) { Int_t buffer1[3] = { -1, -1, -1 }; // 
buffer for sub-branch 1 Int_t buffer2[2] = { -1, -1 }; // buffer for 
sub-branch 2 // create the TTree TFile * outFile = new TFile ( 
"Trees/TreeFile.root", "RECREATE" ); TTree * outTree = new TTree ( 
"MyTree", "MyTree" ); // define the top branch *TBranch * topBranch = 
new TBranchElement ( );****topBranch->SetName ( "MainBr" 
);****topBranch->SetTree ( outTree 
);****outTree->GetListOfBranches()->Add ( topBranch );***** // -> 
correction needed *((TBranchElementModifier*) topBranch)->SetID(-1);* // 
-> dummy leaf *TLeaf * leaf = new TLeafElement ( topBranch, "", -1, 0 
);****topBranch->GetListOfLeaves()->Add(leaf);*** // -> an address is 
required also... // first buffer... *topBranch->SetAddress ( &buffer0[0] 
);* // could be anything ? // topBranch->SetAddress ( topBranch ); // 
also works ?! // define sub-branches TBranch * subBranch1 = new TBranch 
( *outTree*, "Sub1", &buffer1[0], "VarA1/I:VarB1/I:VarC1/I" ); TBranch * 
subBranch2 = new TBranch ( *outTree*, "Sub2", &buffer2[0], 
"VarA2/I:VarB2/I" ); topBranch->GetListOfBranches()->Add ( subBranch1 ); 
topBranch->GetListOfBranches()->Add ( subBranch2 ); //....... // filling 
entries with: outTree->Fill ( ); //....... outTree->Write(); 
outFile->Close(); delete outFile; exit ( 0 ); } |

I tried thissuccessfully also in a more complex example with more
branches with various levels of depth, so I am quite confident that it
works in a general way now.

Thank you very much.
I really appreciate the help your provided and I am grateful of the time
you spent to help solving this question.

Jérôme.

Hi,

Could you reformat the code snippet using the ``` prefix and suffix. As is (at least on my browser) it is hard to parse the code.

Thanks,
Philippe.

Sorry, I repplied by e-mail and did not see the result on the web page.
Here is the code of my previous message.

The points to have it working properly:

  • the correction suggested to modify TBranchElement with SetID;
  • the need to add a dummy leaf to the (top) branch;
  • the need to set a buffer address to the (top) branch, otherwise the sub-branches are not filled; this address does not seem to really matter (I tried both the buffer address of the first sub-branch and the pointer to the branch itself, with the same - successful - result).
//  Needed to correct the behavior of the main branch
struct TBranchElementModifier : public TBranchElement {
   void SetID (int id) { fID = id; }
};
int main ( int argc, char *argv[] )
{
  TFile * outFile = new TFile ( "Trees/TreeFile.root", "RECREATE" );
  TTree * outTree = new TTree ( "MyTree", "MyTree" );

  Int_t buffer1[3] = { -1, -1, -1 };    // buffer for sub-branch 1
  Int_t buffer0[2] = { -1, -1 };        // buffer for sub-branch 2

  // define the top branch
  TBranch * topBranch = new TBranchElement ( );
  topBranch->SetName ( "MainBr" );
  topBranch->SetTree ( outTree );
  outTree->GetListOfBranches()->Add ( topBranch );

  // --> correction needed of branch ID
  ((TBranchElementModifier*)topBranch)->SetID(-1);

  // --> an address is required also... (most likely dummy)
  topBranch->SetAddress ( &buffer0[0] );
  //        it also works with:
  //            topBranch->SetAddress ( topBranch );

  // --> a dummy leaf must be added
  TLeaf * leaf = new TLeafElement ( topBranch, "", -1, 0 );
  topBranch->GetListOfLeaves()->Add(leaf);

  // define the sub-branches
  TBranch * subBranch1 = new TBranch ( outTree, "Sub1", &buffer1[0], "VarA1/I:VarB1/I:VarC1/I" );
  TBranch * subBranch2 = new TBranch ( outTree, "Sub2", &buffer2[0], "VarA2/I:VarB2/I" );

  topBranch->GetListOfBranches()->Add ( subBranch1 );
  topBranch->GetListOfBranches()->Add ( subBranch2 );

  //------------------------------------------------------------
  // Fill the TTree with outTree->Fill()
  //   ...
  //------------------------------------------------------------

  outTree->Write();

  outFile->Close();
  delete outFile;

  return ( 0 );
}

The TTree structure is the following:

  • BR=MainBr Nleaf=1
    • LF= (dummy)
    • BR=Sub1 Nleaf=3
      • LF=VarA1
      • LF=VarB1
      • LF=VarC1
    • BR=Sub2 Nleaf=2
      • LF=VarA2
      • LF=VarB2

Playing the same game for branches (at any level in the TTree structure) to build sub-branches of sub-branches also worked fine: I tested it by mixing several levels of sub-branches (up to 3) in the same TTree.

thanks.

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.