5

I am writing a paper on numerical solutions for ODEs. For the representations of B-series I want to use binary tree-like symbols. I know how to produce them with TiKZ; however, just as in the given picture, i need to use them also within the text or a mathematical expression. As these trees can be represented by a bracket expression, e.g. in the given picture t=[[.,.], [[.]]], it should be possible to write a macro doing this. However, I failed so far - or is there an alternative approach?

Strehmel. Numerik gewöhnlicher Differentialgleichungen. Springer (2012)

2
  • you might save each little tree in a named box, then insert it where you want it with \usebox'. (i know that this works once you have a box. how you get the box is a matter for experimentation; we've done it at ams with small graphics, but they start as little .eps` files.) Commented Nov 25, 2015 at 14:20
  • Do you expect [[.²],[.²]] not to be overlapped? It would be quite hard...
    – Symbol 1
    Commented Nov 25, 2015 at 17:43

3 Answers 3

3

You could use forest to draw the trees. Since it requires a specification of a tree in brackets notation, it seems at least potentially suitable.

Unlike the existing solutions, this one does not require LuaTeX. The examples below were compiled using pdfTeX, but any engine should work.


I present 2 versions:

  1. The Original Edition requires you to write [] for a dot. I consider it safer and less likely to do strange things.

  2. The New Edition allows you to write things like [.,.] normally. I consider it more perilous and more likely to do strange things. However, in minimal testing it does appear to do the job, so it is probably safe if you don't try anything too wildly adventurous with it.

I begin with the New Edition which allows the use of the more natural syntax.


New Edition

If you wish to understand the code, rather than simply using it, please see the explanation of the Original Edition below before attempting to follow my explanation here, as it is easier to understand the creation of a dynamic tree if you see how to create the tree non-dynamically first.

Forest includes the notion of dynamic trees which are built during processing. This allows you to alter the content of nodes, add nodes, move nodes, copy nodes and replace nodes etc. This can be done for single nodes or entire sub-trees.

This means that we can set up our tree to check for . as a node content and replace that node with nothing, while appending an additional node. So, basically,

[.]

will become

[[]]

when the tree is compiled.

The code for this is

      delay={
        if content={.}{
          content=,
          append={[]}
        }{}
      }

This is delayed so that the content of the node is correctly set when the test is done.

This is not enough, though, because we still can't write

[.,.]

because forest will try to interpret the second dot as a key, when it isn't one.

To solve this problem, we define a new forest style, . as follows:

  ./.style={
    delay+={append={[]},}
  },

This appends yet another node to the existing node. In order for the effects to be cumulative, we need to increase the delay. Hence, we need delay+ here rather than simply delay.

This will get the nodes into place, but it will not completely do the job because the formatting we applied to the nodes (circle, fill etc.) is now applied before the additional nodes are created, meaning that those nodes aren't correctly styled.

To overcome this, we can delay the addition of these settings until just before the nodes are typeset by moving them out of the initial for tree=... and applying them to the tree at the appropriate stage:

    before typesetting nodes={
      for tree={
        circle,
        fill,
        minimum width=3pt,
        inner sep=0pt,
      },
    },

Now we can write

\[
  \Bseries{
    [[.,.][[[.]]]]
  }
  =
  [
    \Bseries{
      [.,.]
    }
  ,
    \Bseries{
      [[.]]
    }
  ]
  =
  [
    [
      \Bseries{
        []
      }
    ,
      \Bseries{
        []
      }
    ]
  ,
    [
      \Bseries{
        [.]
      }
    ]
  ]
  =
  [
    [
      \Bseries{
        []
      }
    ,
      \Bseries{
        []
      }
    ]
  ,
    [
      [
        \Bseries{
          []
        }
      ]
    ]
  ]
\]

to produce the same output as with the original example using the Original Edition:

Bseries

Complete code for New Edition:

\documentclass{article}
\usepackage{forest}
\forestset{
  ./.style={
    delay+={append={[]},}
  },
  Bseries/.style={
    for tree={
      grow'=90,
      parent anchor=center,
      child anchor=center,
      s sep=2.5pt,
      if level=0{
        baseline
      }{},
      delay={
        if content={.}{
          content=,
          append={[]}
        }{}
      }
    },
    before typesetting nodes={
      for tree={
        circle,
        fill,
        minimum width=3pt,
        inner sep=0pt,
      },
    },
    before computing xy={
      for tree={
        l=5pt,
      }
    }
  }
}
\newcommand\Bseries[1]{\Forest{Bseries #1}}
\begin{document}
\[
  \Bseries{
    [[.,.][[[.]]]]
  }
  =
  [
    \Bseries{
      [.,.]
    }
  ,
    \Bseries{
      [[.]]
    }
  ]
  =
  [
    [
      \Bseries{
        []
      }
    ,
      \Bseries{
        []
      }
    ]
  ,
    [
      \Bseries{
        [.]
      }
    ]
  ]
  =
  [
    [
      \Bseries{
        []
      }
    ,
      \Bseries{
        []
      }
    ]
  ,
    [
      [
        \Bseries{
          []
        }
      ]
    ]
  ]
\]
\end{document}

Thanks to Symbol 1 for suggesting xparse. If we add

\usepackage{xparse}

then we can define \Bseries as follows:

\DeclareDocumentCommand\Bseries{o}{\Forest{Bseries [#1]}}

which allows us to specify input for the same series as follows:

\[
  \Bseries
    [[.,.][[[.]]]]
  =
  [
    \Bseries
      [.,.]
  ,
    \Bseries
      [[.]]
  ]
  =
  [
    [
      \Bseries
        []
    ,
      \Bseries
        []
    ]
  ,
    [
      \Bseries
        [.]
    ]
  ]
  =
  [
    [
      \Bseries
        []
    ,
      \Bseries
        []
    ]
  ,
    [
      [
        \Bseries
          []
      ]
    ]
  ]
\]

Original Edition

We start by specifying a forest style, Bseries, for our trees since they will otherwise be rather too large and gangly for a comfortable aesthetic.

\forestset{
  Bseries/.style={

Apply the following stuff to the whole tree:

    for tree={

grow up:

      grow'=90,

set the parent and child anchors centrally so that edges are drawn to and from the centres of the circular nodes:

      parent anchor=center,
      child anchor=center,

make sure our nodes are not too small since they'll be empty:

      minimum width=3pt,

eliminate inner sep so that they are not too large:

      inner sep=0pt,

set a reasonable minimum separation for siblings:

      s sep=2.5pt,

ensure all nodes are filled circles:

      circle,
      fill,

anchor the tree at the root node (level zero):

      if level=0{
        baseline
      }{}
    },

The default separation of levels makes for a straggly tree. We want a smaller distance between levels for the whole tree. We delay this in order to make sure that the results of the package's default packing mechanism don't overwrite our setting:

    before computing xy={
      for tree={
        l=5pt,
      }
    }
  }
}

We could use this style as is, but it would be more convenient to define a dedicated command to apply it. So we define \Bseries{<tree specification>} for this purpose:

\newcommand\Bseries[1]{\Forest{Bseries #1}}

Then we can say

\[
  \Bseries{
    [[[][]][[[[]]]]]
  }
  =
  [
    \Bseries{
      [[][]]
    }
  ,
    \Bseries{
      [[[]]]
    }
  ]
  =
  [
    [
      \Bseries{
        []
      }
    ,
      \Bseries{
        []
      }
    ]
  ,
    [
      \Bseries{
        [[]]
      }
    ]
  ]
  =
  [
    [
      \Bseries{
        []
      }
    ,
      \Bseries{
        []
      }
    ]
  ,
    [
      [
        \Bseries{
          []
        }
      ]
    ]
  ]
\]

to produce

Bseries

Complete code for Original Edition:

\documentclass{article}
\usepackage{forest}
\forestset{
  Bseries/.style={
    for tree={
      grow'=90,
      parent anchor=center,
      child anchor=center,
      minimum width=3pt,
      inner sep=0pt,
      s sep=2.5pt,
      circle,
      fill,
      if level=0{
        baseline
      }{}
    },
    before computing xy={
      for tree={
        l=5pt,
      }
    }
  }
}
\newcommand\Bseries[1]{\Forest{Bseries #1}}
\begin{document}
\[
  \Bseries{
    [[[][]][[[[]]]]]
  }
  =
  [
    \Bseries{
      [[][]]
    }
  ,
    \Bseries{
      [[[]]]
    }
  ]
  =
  [
    [
      \Bseries{
        []
      }
    ,
      \Bseries{
        []
      }
    ]
  ,
    [
      \Bseries{
        [[]]
      }
    ]
  ]
  =
  [
    [
      \Bseries{
        []
      }
    ,
      \Bseries{
        []
      }
    ]
  ,
    [
      [
        \Bseries{
          []
        }
      ]
    ]
  ]
\]
\end{document}
9
  • 1
    Seems like one can \let\bracket@emptynodeCharacter=. and then \let\bracket@Parse@token@old=\bracket@Parse@token\long\def\bracket@Parse@token#1{\ifx#1\bracket@emptynodeCharacter\@escapeif{\bracket@Parse@openingBracketFound]}\else\@escapeif{\bracket@Parse@token@old#1}\fi}
    – Symbol 1
    Commented Nov 26, 2015 at 2:55
  • @Symbol1 That's impressive (where is it from? adapted from the source code?). But you don't need anything that low level. You can just test if the content is, say, . and then replace the content and append an additional node. But the problem is that that won't let you write [.,.] because the comma will be parsed as separating the node content from a key, so you'd also need . to be a key doing something similar. And that struck me as messy and hacky and possibly not a good idea.
    – cfr
    Commented Nov 26, 2015 at 3:06
  • 1
    Yes, I just read the definition of \bracket@Parse@token in forest.sty and turns out it is just an \ifx-game. In my case I cannot write [.,.] as well but instead it is possible to write [..]. Nonetheless, one can \let\bracket@donothingCharacter=,...
    – Symbol 1
    Commented Nov 26, 2015 at 3:22
  • @Symbol1 Please see edited answer. Specifically, New Edition at the beginning. This works for me in minimal testing with [.,.]. I also tested [.,.,.] and it still seems to work. (I don't know if this is needed, but I wanted it to work anyway.)
    – cfr
    Commented Nov 26, 2015 at 3:51
  • @Symbol1 You can't say \Bseries{.} with mine, though. At least, I assume not.
    – cfr
    Commented Nov 26, 2015 at 3:53
4

The basic idea is to use TikZ's graph-drawing mechanism. It accepts something like

\graph{/--{/--{/,/},/}};

to draw a graph. So the next step is to translate

  • [ is /--{
  • ] is }
  • , is ,
  • . is /

This is quite hard since TikZ does not like

\graph\bgroup/--\bgroup/--\bgroup/,/\egroup,/\egroup\egroup;

(Internally every pair of { and } will become an argument of some macro, in which case \bgroup and \egroup is not an alternative)

So I define \Bseries to write the content to a file and include it later.

P.S. LuaLaTeX is needed.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{graphs,graphdrawing}
\usegdlibrary{layered}
\begin{document}
\newwrite\tempbseries
\def\Bseries{
    \bgroup
    \immediate\openout\tempbseries=\jobname.bseries
    \immediate\write\tempbseries{\string\tikz[yscale=-.2,xscale=.2]\string\graph[layered layout,nodes={circle,fill,inner sep=1}]\string^\string^;}
    \catcode`\[=\active
    \catcode`\]=\active
    \catcode`\,=\active
    \catcode`\.=\active
    \catcode`\;=\active
    \def\par{\tikzerror{Giving up on this path. Did you forget a semicolon?}}
}
{
    \catcode`\[=\active\gdef[{\immediate\write\tempbseries{/--\string^\string^;}}
    \catcode`\]=\active\gdef]{\immediate\write\tempbseries{\string^\string^=}}
    \catcode`\,=\active\gdef,{\immediate\write\tempbseries{\string,}}
    \catcode`\.=\active\gdef.{\immediate\write\tempbseries{/\string,}}
    \catcode`\;=\active\gdef;{\immediate\write\tempbseries{\string^\string^=\string;}\immediate\closeout\tempbseries\egroup\input{\jobname\string.bseries}}
}

\[
    \Bseries[[.,.],[[.]]];
    =
    [
        \Bseries[.,.];
    ,
        \Bseries[[.]];
    ]
    =
    [
        [
            \Bseries.;
        ,
            \Bseries.;
        ]
    ,
        [
            \Bseries[.];
        ]
    ]
    =
    [
        [
            \Bseries.;
        ,
            \Bseries.;
        ]
    ,
        [
            [
                \Bseries.;
            ]
        ]
    ]
\]
\end{document}
3
  • Thank you very much, I really appreciate your help and efforts. Commented Nov 25, 2015 at 21:11
  • Please, move the \newwrite outside the definition of \Bseries: you're wasting one output stream at each call; some more calls and you'll exhaust them.
    – egreg
    Commented Nov 25, 2015 at 22:25
  • @egreg Good point. (I thought there is some garbage-collection...)
    – Symbol 1
    Commented Nov 26, 2015 at 1:25
3

A variant of Symbol1's answer that doesn't require auxiliary files. However, for technical reasons, instead of a period a * is used.

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{graphs,graphdrawing}
\usegdlibrary{layered}

\newcommand{\setupbseriesactive}{%
  \begingroup\lccode`~=`[ \lowercase{\endgroup\def~{/--\iftrue{\else}\fi}}%
  \begingroup\lccode`~=`] \lowercase{\endgroup\def~{\iffalse{\else}\fi}}%
  \begingroup\lccode`~=`* \lowercase{\endgroup\def~{/}}%
  \catcode`[=\active \catcode`]=\active \catcode`*=\active
}
\newcommand\Bseries{\begingroup\setupbseriesactive\Bseriesaux}
\def\Bseriesaux#1;{%
  \edef\Bseriestemp{%
    \noexpand\tikz[yscale=-.2,xscale=.2]%
    \noexpand\graph[layered layout,nodes={circle,fill,inner sep=1}]{#1};%
  }\,\Bseriestemp\,%
  \endgroup
}

\begin{document}

\[
    \Bseries[[*,*],[[*]]];
    =
    [
        \Bseries[*,*];
    ,
        \Bseries[[*]];
    ]
    =
    [
        [
            \Bseries*;
        ,
            \Bseries*;
        ]
    ,
        [
            \Bseries[*];
        ]
    ]
    =
    [
        [
            \Bseries*;
        ,
            \Bseries*;
        ]
    ,
        [
            [
                \Bseries*;
            ]
        ]
    ]
\]
\end{document}

enter image description here

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .