# How do I split a forest tree into two trees with proper vertical alignment?

\documentclass[oneside,12pt]{article}
\usepackage{geometry}
\usepackage{microtype}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{lmodern}

\usepackage{enumitem}
\setlist[enumerate,1]{label=\bfseries\Alph*,align=left,leftmargin=*,
labelsep=1.5em}
\setlist[enumerate,2]{label=\arabic*.,labelindent=1em,labelsep=1.5em,
leftmargin=*}

\usepackage{amssymb}
\usepackage{amsmath}
\usepackage{amsthm}
\theoremstyle{definition}
\newtheorem*{sol}{Solution}

\usepackage{forest}
\forestset{
ass/.append style={
before computing xy={l=\baselineskip},
no edge
},
prooftree/.style={
for tree={
child anchor=north,
parent anchor=south
}
}
}

\begin{document}
\subsection*{Exercises 29}
\begin{enumerate}
\item Use QL trees to evaluate the entailment claims (1) to (10) in Exercises
28A.
\begin{enumerate}
\item $\forall x(Fx\supset Gx)\vDash\forall x(Gx\supset Fx)$

\begin{forest}
prooftree
[$\forall x(Fx\supset Gx)$
[$\neg\forall x(Gx\supset Fx)\checkmark$,ass
[$\exists x\neg(Gx\supset Fx)\checkmark$,ass
[$\neg(Ga\supset Fa)\checkmark$,ass
[$Ga$,ass
[$\neg Fa$,ass
[$(Fa\supset Ga)\checkmark$,ass
[$\neg Fa$] [$Ga$]]]]]]]]
\end{forest}

$\neg Fa$ and $Ga$ make the premisses and the negation of conclusion true.
Thus, the entailment is invalid.
\item $\forall x(Fx\supset Gx)\vDash\forall x(\neg Gx\supset\neg Fx)$

\begin{forest}
prooftree
[$\forall x(Fx\supset Gx)$
[$\neg\forall x(\neg Gx\supset\neg Fx)\checkmark$,ass
[$\exists x\neg(\neg Gx\supset\neg Fx)\checkmark$,ass
[$\neg(\neg Ga\supset\neg Fa)\checkmark$,ass
[$\neg Ga$,ass
[$\neg\neg Fa$,ass
[$(Fa\supset Ga)\checkmark$,ass
[$\neg Fa$ [*,ass]] [$Ga$ [*,ass]]]]]]]]]
\end{forest}

Since the tree closes, the entailment is valid.
\item $\forall x\exists yLxy\vDash\forall y\exists xLyx$

\begin{forest}
prooftree
[$\forall x\exists yLxy$
[$\neg\forall y\exists xLyx\checkmark$,ass
[$\exists y\neg\exists xLyx\checkmark$,ass
[$\neg\exists xLax\checkmark$,ass
[$\forall x\neg Lax$,ass
[$\exists yLay\checkmark$,ass
[$Lab$,ass]]]]]]]
\end{forest}

\begin{forest}
prooftree
[$\neg Lab$,ass [*,ass]]
\end{forest}
\item $\forall x((Fx\wedge Gx)\supset Hx)\vDash\forall x(Fx\supset(Gx\supset Hx))$
\item $(\forall xFx\vee\forall xGx)\vDash\forall(Fx\vee Gx)$
\item $\forall x(Fx\supset Gx),\forall x(\neg Gx\supset Hx)\vDash \forall x(Fx\supset\neg Hx)$
\item $\exists x(Fx\wedge Gx),\forall x(\neg Hx\supset\neg Gx)\vDash \exists x(Fx\wedge Hx)$
\item $\forall x\exists y(Fy\supset Gx)\vDash\forall y\exists x(Gx\supset Fy)$
\item $\forall x\forall y(Lxy\supset Lyx)\vDash\forall xLxx$
\item $\forall x(\exists yLxy\supset\forall zLzx)\vDash\forall x\forall y (Lxy\supset Lyx)$
\end{enumerate}
\end{enumerate}
\end{document}


Usually, I have to manually split a forest tree into two to emulate page breaking in a forest tree. However, the second tree doesn't vertically align with the first as in the picture below. How can I vertically align them together? @cfr

• Why are you claiming semantic entailment when the proof is syntactic? It is rather late, admittedly....) – cfr Mar 2 '16 at 3:47
• The textbook introduced some idiosyncrasies that don't make sense to people who didn't read it. Don't mind it. – crocket Mar 3 '16 at 2:24
• Oh, OK. Text books. You can't expect them to get it right ;). – cfr Mar 3 '16 at 3:33

In my opinion, vertical alignment will be hard to achieve automatically if the two parts of the proof tree are not produced by the same forest environment. Thus, the plan is twofold: customize draw tree stage to produce a two tikz pictures, and add an invisible path to the narrower one to create the alignment.

UPDATE: WARNING: this solution only works with Forest v2. The new features of v2 used below are: customizing draw tree via processing order nodewalks; aggregate functions; registers.

UPDATE: in depth explanation of draw tree stage customization. The Forest command that actually produces a tikzpicture containing the drawing instructions is draw tree. This command respects the contents of draw tree processing order nodewalk style, which tells it which nodes (and edges and extra tikz material) to actually draw (and in which order). Normally, this nodewalk is just tree, so the entire tree is drawn. If we set it to something "less" than a tree, only a part of the tree will get drawn, and we can use this to yield a (potential) pagebreak point. In the first example below, the draw tree processing order nodewalk critically depends on the fact that the tree doesn't branch, as it only draws the ancestors of the chosen node as the first part of the output.

UPDATE: in depth explanation of achieving alignment. We measure the minimum and maximum x position of every node in the tree (x is the x position of the node anchor, min_x and max_x are the x-extents of the node in the coordinate system with node anchor as the origin). We use .min and .max aggregate functions to compute the minimum/maximum for the entire tree. The results are stored in temporary dimension registers. Style to widest adds a dummy path of the appropriate width to the extra tikz material of a node, creating the alignment.

\def\hiddenparcommand{\par}
\begin{forest}
before drawing tree={
tempdima/.min={x()+min_x()}{tree},
tempdimb/.max={x()+max_x()}{tree},
},
to widest/.style={
tikz+={\path (\forestregister{tempdima}, \forestoption{y}) -- (\forestregister{tempdimb}, \forestoption{y});},
},
draw tree stage/.style={
for nodewalk={draw tree processing order/.style={fake={name=break page before}, ancestors}}{},
for root'={draw tree},
TeX={\hiddenparcommand},
for nodewalk={draw tree processing order/.style={tree}}{},
for name={break page before}{to widest,draw tree},
},
prooftree
[$\forall x\exists yLxy$
[$\neg\forall y\exists xLyx\checkmark$,ass
[$\exists y\neg\exists xLyx\checkmark$,ass
[$\neg\exists xLax\checkmark$,ass
[$\forall x\neg Lax$,ass
[$\exists yLay\checkmark$,ass
[$Lab$,ass,
[$\neg Lab$,ass,alias=break page before,
[*,ass
]]]]]]]]]
\end{forest}


UPDATE: the case of a branching tree. The only real difference here is in the draw tree processing order nodewalk. In the example below, the breakpoint is determined by the level (hierarhichal depth) of the node. (Depending on how the tree looks like and what we're trying to achieve (think animation!), one might employ various tricks to "partition" the tree.)

\documentclass{article}

\usepackage{forest}

\begin{document}
\def\hiddenparcommand{\par}
\begin{forest}
where n children={1}{
l sep=0,for children={no edge,l=0}
}{
parent anchor=south,for children={child anchor=north}
},
to widest/.style={
tikz+={\path (\forestregister{tempdima}, \forestoption{y}) -- (\forestregister{tempdimb}, \forestoption{y});},
},
draw tree stage/.style={
for root'={
tempdima/.min={x()+min_x()}{tree},
tempdimb/.max={x()+max_x()}{tree},
for tree={to widest},
},
for nodewalk={draw tree processing order/.style={level<=1}}{},
for root'={draw tree},
TeX={\hiddenparcommand---pagebreak---\hiddenparcommand},
for nodewalk={draw tree processing order/.style={level>=2}}{},
for root'={draw tree},
},
[R
[AAAA1[A2[A3]]]
[B1[B2[B3]]]
]
\end{forest}
\end{document}


UPDATE: forest v1 solution. The idea is the same, but the implementation is a tad more difficult. The min and max x position must be calculated manually (using the trick from the front page tree of the v1 manual). Which nodes are drawn in which part is regulated by phantom; there is an additional complication due to a bug I haven't noticed before: extra tikz material is typeset even if the node is phantom.

\documentclass{article}
\usepackage{forest}

\begin{document}
\def\hiddenparcommand{\par}
\begin{forest} % v1
where n children={1}{
l sep=0,for children={no edge,l=0}
}{
parent anchor=south,for children={child anchor=north}
},
to widest/.style={
tikz+/.wrap 3 pgfmath args={\path (##1pt,##3pt) -- (##2pt,##3pt);}{x("leftmost")+min_x("leftmost")}{x("rightmost")+max_x("rightmost")}{y},
},
draw tree stage/.style={
for root'={
alias=leftmost, alias=rightmost,
for descendants={
if={x()+min_x()<x("leftmost")+min_x("leftmost")}{alias=leftmost}{},
if={x()+max_x()>x("rightmost")+max_x("rightmost")}{alias=rightmost}{},
},
where={level()<=1}{to widest}{phantom=true},
draw tree,
TeX={\hiddenparcommand---pagebreak---\hiddenparcommand},
where={level()<=1}{tikz={},phantom=true}{to widest,phantom=false},
draw tree,
}
},
[R
[AAAA1[A2[A3]]]
[B1[B2[B3]]]
]
\end{forest}
\end{document}

• What if the tree forks? – crocket Mar 1 '16 at 22:39
• Neat! @crocket You can't ping me like that, by the way. It doesn't work. – cfr Mar 2 '16 at 3:46
• If I center every forest tree horizontally, would trees align together even in the presence of forks? – crocket Mar 2 '16 at 4:02
• They would, if the trees are completely symmetric. – Sašo Živanović Mar 2 '16 at 11:00
• @cfr In that piece of code, level< / level> is a nodewalk (multi-)step (not a pgfmath function). As any other key, it must be used as key=value. level>1 would ask pgfkeys to execute key (i.e. step) level>1 without a value. (Key names (relative) level (reversed)> (and other keys ending with > or <) were given the or equal to semantics so that the normal usage level>=1 is "wysiwyg".) – Sašo Živanović Mar 24 '16 at 10:12

Here is Sašo Živanović's answer turned into a style. The style picks up the level automatically. It takes one optional argument which is the stuff to typeset before resuming the drawing of the tree. Branches are marked if they are to be continued or are continuations, although obviously this kind of stuff is optional and depends on what you need/want. (Right now, it tells you the tree continues in a zillion ways, which is no doubt overkill.)

\documentclass{article}
\usepackage{forest}
% Sašo Živanović: https://tex.stackexchange.com/a/296771/
\def\hiddenparcommand{\par}
\forestset{%
declare count register={split here level},
declare toks register={split here interject},
split here level'=-1,
split here interject={},
to widest/.style={
tikz+={\path (\forestregister{tempdima}, \forestoption{y}) -- (\forestregister{tempdimb}, \forestoption{y});},
},
split here/.style={%
split here level'/.option=level,
split here interject={#1},
split tree
},
split tree/.code={%
\forestset{%
draw tree stage/.style={
for root'={
tempdima/.min={x()+min_x()}{tree},
tempdimb/.max={x()+max_x()}{tree},
for tree={%
to widest,
if level/.wrap pgfmath arg={{####1}{label={[text=gray, anchor=north, font=\scriptsize]below:{[cont.]}}}{}}{split_here_level},
if level/.wrap pgfmath arg={{####1}{edge={densely dotted, gray}, edge label={node [font=\scriptsize, pos=0, anchor=south, text=gray] {[cont.]}}}{}}{int((split_here_level)+1)},
},
},
for nodewalk/.wrap pgfmath arg={{draw tree processing order/.style={level<=####1}}{}}{split_here_level},
for root'={draw tree},
TeX/.wrap pgfmath arg={\hiddenparcommand ####1\hiddenparcommand}{split_here_interject},
for nodewalk/.wrap pgfmath arg={{draw tree processing order/.style={level>=####1}}{}}{(split_here_level)+1},
for root'={draw tree},
},
}
}
}
\begin{document}
\begin{forest}
[R
[AAAA1[A2[A3]]]
[B1[B2[B3]]]
]
\end{forest}

\begin{forest}
[R
[AAAA1[A2, split here={to be continued \dots}[A3]]]
[B1[B2[B3]]]
]
\end{forest}

\begin{forest}
[R
[AAAA1, split here={to be continued \dots}[A2[A3]]]
[B1[B2[B3]]]
]
\end{forest}

\begin{forest}
[R
[AAAA1[A2[A3]]]
[B1[B2, split here={to be continued \dots}[B3]]]
]
\end{forest}

\end{document}


# EDIT

Proof of concept version allowing multiple splits e.g. for explanations.

\documentclass[12pt]{article}
\usepackage{geometry}
\usepackage{microtype}
\usepackage[utf8]{inputenc}
\usepackage[T1]{fontenc}
\usepackage{lmodern}
\usepackage{enumitem}
\setlist[enumerate,1]{label=\bfseries\Alph*,align=left,leftmargin=*, labelsep=1.5em}
\setlist[enumerate,2]{label=\arabic*.,labelindent=1em,labelsep=1.5em, leftmargin=*}
\usepackage{amssymb}
\usepackage{amsmath}
\usepackage{amsthm}
\theoremstyle{definition}
\newtheorem*{sol}{Solution}
\usepackage{forest}
% Sašo Živanović: https://tex.stackexchange.com/a/296771/
\newcommand\hiddenparcommand{\par\noindent}
\newcommand\hiddencommacommand{, }
\forestset{%
declare keylist register={split here levels},
declare keylist register={split here interjects},
declare toks register=split here toks,
split here levels={},
split here interjects={},
to widest/.style={
tikz+={\path (\forestregister{tempdima}, \forestoption{y}) -- (\forestregister{tempdimb}, \forestoption{y});},
},
hide commas/.style={%
split here toks+={\hiddencommacommand},
split here toks+={#1},
},
split here/.style={%
split here levels+/.option=level,
split={#1}{,}{split here toks,hide commas},
split here interjects/.register=split here toks,
split tree
},
split tree/.code={%
\forestset{%
draw tree stage/.style={
for root'={
tempdima/.min={x()+min_x()}{tree},
tempdimb/.max={x()+max_x()}{tree},
for tree={%
to widest,
},
},
tempcountb'=-1,
do until={%
strequal((split_here_levels),"")
}{%
tempkeylistb'={},
tempkeylista'={},
split register={split here levels}{,}{tempcounta,tempkeylistb+},
split register={split here interjects}{,}{temptoksa,tempkeylista+},
split here levels'/.register=tempkeylistb,
split here interjects'/.register=tempkeylista,
% Sašo Živanović: http://chat.stackexchange.com/transcript/message/28484520#28484520
for nodewalk/.wrap 2 pgfmath args={{draw tree processing order/.style={filter={level<=########1}{level()>########2}}}{}}{tempcounta}{tempcountb},
for root'={draw tree},
TeX/.wrap pgfmath arg={\hiddenparcommand ########1\hiddenparcommand}{temptoksa},
tempcountb'/.register=tempcounta,
},
for nodewalk/.wrap pgfmath arg={{draw tree processing order/.style={level>=####1}}{}}{(tempcountb)+1},
for root'={draw tree},
},
}
},
ass/.append style={
before computing xy={l=\baselineskip},
no edge
},
prooftree/.style={
for tree={
child anchor=north,
parent anchor=south
}
},
}

\begin{document}
\subsection*{Exercises 29}
\begin{enumerate}
\item Use QL trees to evaluate the entailment claims (1) to (10) in Exercises
28A.
\begin{enumerate}
\item $\forall x(Fx\supset Gx)\vDash\forall x(Gx\supset Fx)$

\begin{forest}
prooftree
[$\forall x(Fx\supset Gx)$
[$\neg\forall x(Gx\supset Fx)\checkmark$,ass
[$\exists x\neg(Gx\supset Fx)\checkmark$,ass
[$\neg(Ga\supset Fa)\checkmark$,ass
[$Ga$,ass
[$\neg Fa$,ass
[$(Fa\supset Ga)\checkmark$,ass
[$\neg Fa$] [$Ga$]]]]]]]]
\end{forest}

$\neg Fa$ and $Ga$ make the premisses and the negation of conclusion true.
Thus, the entailment is invalid.
\item $\forall x(Fx\supset Gx)\vDash\forall x(\neg Gx\supset\neg Fx)$

\begin{forest}
prooftree
[$\forall x(Fx\supset Gx)$
[$\neg\forall x(\neg Gx\supset\neg Fx)\checkmark$,ass
[$\exists x\neg(\neg Gx\supset\neg Fx)\checkmark$,ass
[$\neg(\neg Ga\supset\neg Fa)\checkmark$,ass
[$\neg Ga$,ass
[$\neg\neg Fa$,ass
[$(Fa\supset Ga)\checkmark$,ass
[$\neg Fa$ [*,ass]] [$Ga$ [*,ass]]]]]]]]]
\end{forest}

Since the tree closes, the entailment is valid.
\item $\forall x\exists yLxy\vDash\forall y\exists xLyx$

\begin{forest}
prooftree
[$\forall x\exists yLxy$
[$\neg\forall y\exists xLyx\checkmark$,ass, split here={This tree gets split later but not quite yet, as you see.}
[$\exists y\neg\exists xLyx\checkmark$,ass
[$\neg\exists xLax\checkmark$,ass
[$\forall x\neg Lax$,ass, split here={Continued}
[$\exists yLay\checkmark$,ass
[$Lab$,ass
[$\neg Lab$,ass [*,ass]]]]]]]]
]
\end{forest}

\item $\forall x((Fx\wedge Gx)\supset Hx)\vDash\forall x(Fx\supset(Gx\supset Hx))$
\item $(\forall xFx\vee\forall xGx)\vDash\forall(Fx\vee Gx)$
\item $\forall x(Fx\supset Gx),\forall x(\neg Gx\supset Hx)\vDash \forall x(Fx\supset\neg Hx)$
\item $\exists x(Fx\wedge Gx),\forall x(\neg Hx\supset\neg Gx)\vDash \exists x(Fx\wedge Hx)$
\item $\forall x\exists y(Fy\supset Gx)\vDash\forall y\exists x(Gx\supset Fy)$
\item $\forall x\forall y(Lxy\supset Lyx)\vDash\forall xLxx$
\item $\forall x(\exists yLxy\supset\forall zLzx)\vDash\forall x\forall y (Lxy\supset Lyx)$
\end{enumerate}
\end{enumerate}
\end{document}