# Drawing ramified coverings with tikz

I want to draw a diagram similar to this one:

For that I started with the following code:

\begin{tikzpicture}
\draw (0,0) node {$Y$};
\draw (0,2) node {$X$};
\draw[<-] (0,0.35) -- (0,1.65) node[left, midway] {$f$};
\draw[thick] (1,0) -- (7,0);
\draw[thick] (1,2) -- (7,2);
\draw[thick] (1,2.5) -- (7,2.5);
\draw[thick] (1,1.5) -- (7,1.5);
\end{tikzpicture}


The only thing that I don't know how to do is the curvy parts. I would appreciate some indication.

This uses the same in and out trick as Skillmon and puts it into a style dip, which takes as arguments the horizontal position and the depth, where the sign decides whether the dip is a dip (minus) or a bump (plus). (Let me also mention that you do not need to do something like \draw[<-] (0,0.35) -- (0,1.65) node[left, midway] {$f$};. If you name the nodes, you can just do \draw[<-] (Y) -- (X) node[left, midway] {$f$}; and TikZ will make sure to shorten the arrow without you having to compute the coordinates.)

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{positioning}
\newcounter{dip}
\begin{document}
\begin{tikzpicture}[dip/.style args={#1/#2}{/utils/exec=\stepcounter{dip},
insert path={%
coordinate (aux1) ({#1-abs(#2)},0) coordinate (aux2) ({#1+abs(#2)},0) coordinate (aux3)
(aux1) -- (aux2|-aux1) to[out=0,in=180]
++({abs(#2)},#2) coordinate(dip-\the\value{dip}) to[out=0,in=180]  (aux3|-aux1)
}}]
\begin{scope}[thick,local bounding box=dips]
\draw (1,2.5) [dip=5.5cm/-2.5mm]-- (7,2.5);
\fill (dip-1) circle[radius=2pt] node[right=3pt]{$b=1$};
\draw (1,2) [dip/.list={2.5cm/-2.5mm,5.5cm/2.5mm}] -- (7,2);
\fill (dip-2) circle[radius=2pt] node[right=3pt]{$b=1$};
\draw (1,1.5) [dip/.list={2.5cm/2.5mm,5.5cm/-5mm}]  -- (7,1.5);
\fill (dip-5) circle[radius=2pt] node[above right=0pt and 5pt]{$b=2$};
\draw (1,1) -- (7,1);
\draw (1,0.5) [dip=5.5cm/5mm]  -- (7,0.5);
\end{scope}
\node[left=2pt of dips.west] (X) {$X$};
\draw (7,-0.5) -- (1,-0.5)  node[left=2pt] (Y) {$Y$};
\draw[<-] (Y) -- (X) node[left, midway] {$f$};
\foreach \X in {1,2}
{
\draw[dashed] (dip-\X|-Y) -- (dip-\X|-0,2.75);
}
\end{tikzpicture}
\end{document}


Just for fun: a variation for Skillmon.

\documentclass[tikz,border=3.14mm]{standalone}
\usetikzlibrary{positioning}
\newcounter{dip}
\begin{document}
\begin{tikzpicture}[dip/.style={/utils/exec=\stepcounter{dip},
insert path={%
to[out=0,in=180]
++(0.25,#1) node[bullet](dip-\the\value{dip}){}
to[out=0,in=180] ++(0.25,-1*#1)
}},bullet/.style={circle,fill,inner sep=1.5pt}]
\begin{scope}[thick,local bounding box=dips]
\draw (1,2.5) -- (5.25,2.5) [dip=-2.5mm]-- (7,2.5);
\node[right=3pt of dip-1]{$b=1$};
\draw (1,2)  -- (2.25,2) [dip=-2.5mm] -- (5.25,2) [dip=2.5mm] --(7,2);
\node[right=3pt of dip-2]{$b=1$};
\draw (1,1.5) -- (2.25,1.5) [dip=2.5mm] --(5.25,1.5) [dip=-5mm]  -- (7,1.5);
\node[above right=-1.5pt and 5pt of dip-5]{$b=2$};
\draw (1,1) -- (7,1);
\draw (1,0.5) --(5.25,0.5) [dip=5mm] -- (7,0.5);
\end{scope}
\node[left=2pt of dips.west] (X) {$X$};
\draw (7,-0.5) -- (1,-0.5)  node[left=2pt] (Y) {$Y$};
\draw[<-] (Y) -- (X) node[left, midway] {$f$};
\foreach \X in {1,2}
{
\draw[dashed] (dip-\X|-Y) node[bullet]{} -- (dip-\X|-0,2.75);
}
\end{tikzpicture}
\end{document}


• Very nice automation. Just out of curiosity (coding TikZ is still something I'm not good at), is it possible to also draw the filled circles inside the dip style? – Skillmon Mar 24 at 11:21
• @Skillmon Yes, of course. E.g. dip/.style args={#1/#2}{/utils/exec=\stepcounter{dip}, insert path={% coordinate (aux1) ({#1-abs(#2)},0) coordinate (aux2) ({#1+abs(#2)},0) coordinate (aux3) (aux1) -- (aux2|-aux1) to[out=0,in=180] ++({abs(#2)},#2) node[circle,fill,inner sep=1.5pt](dip-\the\value{dip}){} to[out=0,in=180] (aux3|-aux1) }}. – marmot Mar 24 at 12:16

The following is a pretty manual way to do this. I only did it for the first two lines, I hope you can apply it to the other occurrences. It uses the in and out keys of the to path construction:

\documentclass[tikz]{standalone}

\begin{document}
\begin{tikzpicture}
\draw (0,0) node {$Y$};
\draw (0,2) node {$X$};
\draw[<-] (0,0.35) -- (0,1.65) node[left, midway] {$f$};
\draw[thick] (1,2.5) -- (7,2.5) coordinate(a);
\draw[thick] (1,2) -- (7,2) coordinate(b);
\draw[thick] (1,1.5) -- (7,1.5) coordinate(c);
\draw[thick] (1,0) -- (7,0) coordinate(d);
\draw[thick]
(a) ++(.25,-.25) coordinate(ab) to[out=180,in=0] (a)
(ab) to[out=180,in=0] (b)
(ab) to[out=0,in=180] ++(.25,.25)
(ab) to[out=0,in=180] ++(.25,-.25)
;
\filldraw
(ab) circle(.05)
;
\end{tikzpicture}
\end{document}


This is a proof of concept how to use parser library to define the following "language" :

• = stay at the same level
• u goes half up
• U goes one up
• d goes half down
• D goes one down
• x plot a (red) dot
• . end

Here is the code.

\documentclass[tikz,border=7pt]{standalone}
\usepgfmodule{parser}
% -----------------------------
% macro that add #1 to the current path when used inside \pgfextra
\def\insertpath#1{\tikzset{insert path={#1}}}
% define the parser "sheet path"
\pgfparserdef{sheet path}{initial}{the character =}{\insertpath{ -- ++(1, 0)}}
\pgfparserdef{sheet path}{initial}{the letter u}{\insertpath{ to[out=0,in=180] ++(1, .5)}}
\pgfparserdef{sheet path}{initial}{the letter U}{\insertpath{ to[out=0,in=180] ++(1,  1)}}
\pgfparserdef{sheet path}{initial}{the letter d}{\insertpath{ to[out=0,in=180] ++(1,-.5)}}
\pgfparserdef{sheet path}{initial}{the letter D}{\insertpath{ to[out=0,in=180] ++(1, -1)}}
\pgfparserdef{sheet path}{initial}{the letter x}{\insertpath{ node[red,scale=4]{.}}}
\pgfparserdef{sheet path}{initial}{the character .}{\pgfparserswitch{final}}
% the sheet interface macro
\def\sheet#1.{\pgfextra{\pgfparserparse{sheet path}#1.}}
% -----------------------------
\begin{document}
\tikz\draw[thick]
(0,-1) \sheet======du=.
(0,-2) \sheet==du==uxd=.
(0,-3) \sheet==uxd==DU=.
(0,-4) \sheet=========.
(0,-5) \sheet======UxD=.;
\end{document}


• +1, I didn't know parser, wow! – CarLaTeX Mar 24 at 10:02

I think Bézier curves can do the task. It can make sure that the starting points and the ending points of all curves are placed with a consistent ratio.

\documentclass[tikz]{standalone}
\def\innersep{.5em}
\usetikzlibrary{patterns}
\begin{document}
\begin{tikzpicture}[y=\baselineskip+2*\innersep]
\draw (0,0)--(6.5,0);
\draw (0,1.5)--(6.5,1.5);
\draw (0,1)--(4.75,1) .. controls (4.9,1) and (4.85,1.5) .. (5,1.5) .. controls (5.15,1.5) and (5.1,1) .. (5.25,1)--(6.5,1);
\draw (0,2)--(1.25,2) .. controls (1.4,2) and (1.35,2.25) .. (1.5,2.25) .. controls (1.65,2.25) and (1.6,2) .. (1.75,2)--(4.75,2) .. controls (4.9,2) and (4.85,1.5) .. (5,1.5) .. controls (5.15,1.5) and (5.1,2) .. (5.25,2)--(6.5,2);
\draw (0,2.5)--(1.25,2.5) .. controls (1.4,2.5) and (1.35,2.25) .. (1.5,2.25) .. controls (1.65,2.25) and (1.6,2.5) .. (1.75,2.5)--(4.75,2.5) .. controls (4.9,2.5) and (4.85,2.75) .. (5,2.75) .. controls (5.15,2.75) and (5.1,2.5) .. (5.25,2.5)--(6.5,2.5);
\draw (0,3)--(4.75,3) .. controls (4.9,3) and (4.85,2.75) .. (5,2.75) .. controls (5.15,2.75) and (5.1,3) .. (5.25,3)--(6.5,3);
\fill (1.5,2.25) circle (1.5pt) (5,1.5) circle (1.5pt) (5,2.75) circle (1.5pt) (1.5,0) circle (1.5pt) (5,0) circle (1.5pt);
\draw[dashed] (1.5,0)--(1.5,3.25) (5,0)--(5,3.25);
\draw (1.5,2.25) node[right=1ex] {$b=1$};
\draw (5,1.75) node[right=1ex] {$b=2$};
\draw (5,2.75) node[right=1ex] {$b=1$};
\draw (-0.5,0) node (n) {$N$};
\draw (-0.5,2.25) node (m) {$M$};
\draw[-latex] (m)--(n) node[midway,left] {$f$};
\end{tikzpicture}
\end{document}


This is a proof of concept how to use plot[smooth] coordinates to do this.

\documentclass[tikz,border=7pt]{standalone}
\begin{document}
\begin{tikzpicture}[yscale=.5,
sheet/.style={red,thick,smooth},
point/.style={insert path={node[scale=4]{.}}}]
% ---- sheets ----
\foreach[count=\i] \pts in {
{(0, 0) (1, 0) (2, 0) (3, 0) (6, 0) (7,-1) (8, 0) (9, 0)},%
{(0, 0) (1, 0) (2,-1) (3, 0) (6, 0) (7, 1) (8, 0) (9, 0)},%
{(0, 0) (1, 0) (2, 1) (3, 0) (6, 0) (7,-2) (8, 0) (9, 0)},%
{(0, 0) (1, 0) (2, 0) (3, 0) (6, 0) (7, 0) (8, 0) (9, 0)},%
{(0, 0) (1, 0) (2, 0) (3, 0) (6, 0) (7, 2) (8, 0) (9, 0)},%
{(0,0)},%
{(0, 0) (9, 0)}%
}{
\draw[yshift=-2*\i cm,sheet] plot coordinates {\pts};
}
% ---- singular points and labels ----
\path
(2,-5) [point] (3,-5) node{$b=1$}
(7,-3) [point] (8,-3) node{$b=1$}
(7,-8) [point] (8,-7.5) node{$b=2$}
;
% ---- vertical lines ----
\draw[dashed]
(2,0) -- +(0,-14) [point]
(7,0) -- +(0,-14) [point]
;
\end{tikzpicture}
\end{document}


Edit notes:

• spaces can now be handled, but are ignored by default

• alternative syntax for \myparserdef added

Inspired by Kpym's answer but thinking that the possibilities of pgfparser are insufficient, I wrote my own parser, that should be similar to pgf's, but allows the parsed elements to grab arguments. The following arguments are supported:

• m a mandatory argument
• r{<delim>} a mandatory argument delimited by <delim>
• o an optional argument in brackets (you can test for it with \myIfNoValueTF)
• O{<default>} an optional argument defaulting to <default>
• d<delim1><delim2> an optional argument delimited by <delim1> and <delim2> (you can test for it with \myIfNoValueTF)
• D<delim1><delim2>{<default>} an optional argument delimited by <delim1> and <delim2> defaulting to <default>
• t<token> an optional token (you can test for it with \myIfBooleanTF)

Despite the fact that the argument names are inspired by xparse, none of them care for balanced delimiters (e.g., [[ab]] would not be grabbed as [ab], but as [ab with an orphaned ], so you'd have to use [{[ab]}], this is like the LaTeX2e behaviour). Also all arguments are long by nature unlike xparse where you'd need to specify that using +.

You can define a parser using \myparserdef{<name>}{<state>}{<meaning>}[<args>]{<code>}, with <name> the name of the parser, <state> the state, <meaning> the meaning of a token, <args> an argument string (built from the arguments listed above, up to 9 arguments are supported) and <code> the code which should be executed for that combination of <name>, <state> and <meaning>. You can switch states using \myparserstate{<state>}, if you switch to final parsing is ended, the initial state is initial. All in all this is pretty similar to pgfparsers way of doing things. The biggest difference is, that my parser ignores blanks by default, however you can define an action for blanks with \myparserdef{<name>}{<state>}{blank space}[<args>]{<code>}. Many consecutive blanks are considered as one.

You can also use a different syntax for \myparserdef namely: \myparserdef{<name>}{<state>}<token>[<args>]{<code>} with <token> being a single token not surrounded by {} (spaces are ignored here, it is not checked whether <token> is really a single token, so be careful). In that case the same will be done as if you'd typed the \meaning of the <token>, so \myparserdef{foo}{bar}a{baz} does the same as \myparserdef{foo}{bar}{the letter a}{baz}.

A parser is executed using \myparserrun{<name>}. A parser needs at least one rule, else an error is thrown.

This parser system allows me to place coordinates inside of \sheet, which was the main reason why I found pgfparser insufficient.

I did all of this without using any packages, only LaTeX-Kernel macros, just for procrastinating reasons. Using for example xparse would shorten the code considerably.

\documentclass[tikz,border=7pt]{standalone}

% my parser >>>
\makeatletter
% logic helpers >>>
\long\def\myfi@An#1\fi#2{\fi}
\long\def\myfi@Ay#1\fi#2{\fi#2}
\long\def\myfi@By\fi#1{\fi#1}
\long\def\myfi@BTb\fi#1#2#3{\fi#2}
% <<<
% mynewdef >>>
\@ifdefinable\myifundefTF%>>>
{%
\def\myifundefTF#1%
{%
\ifdefined#1%
\myfi@Ay
\else
\myfi@BTb
\fi
{%
\ifx#1\relax
\myfi@BTb
\fi
\@secondoftwo
}%
}%
}%<<<
\@ifdefinable\mynewdef%>>>
{%
\protected\def\mynewdef{\@ifnextchar[{\mynewdef@a}{\mynewdef@a[]}}%
}%<<<
\@ifdefinable\mynewdef@a%>>>
{%
\long\def\mynewdef@a[#1]#2#3#{\mynewdef@b{#1}#2{#3}}%
}%<<<
\@ifdefinable\mynewdef@b%>>>
{%
\protected\long\def\mynewdef@b#1#2#3#4%
{%
\myifundefTF#2%
{%
#1\def#2#3{#4}%
}
{%
}
}%
}%<<<
\mynewdef\myifcsundefTF#1%>>>
{%
\ifcsname #1\endcsname
\myfi@Ay
\else
\myfi@BTb
\fi
{%
\expandafter\ifx\csname #1\endcsname\relax
\myfi@BTb
\fi
\@secondoftwo
}%
}%<<<
% <<<
% opt arg parsing >>>
\mynewdef[\long]\my@ifmark#1%>>>
{%
\ifx\my@mark#1%
\myfi@BTb
\fi
\@secondoftwo
}%<<<
\mynewdef[\protected\long]\myoarg@oarg#1%>>>
{%
\@ifnextchar[{\myoarg@oarg@{#1}}{#1{\my@mark}}%
}%<<<
\mynewdef[\long]\myoarg@oarg@#1[#2]%>>>
{%
#1{#2}%
}%<<<
\mynewdef[\protected\long]\myoarg@Oarg#1#2%>>>
{%
\@ifnextchar[{\myoarg@oarg@{#2}}{#2{#1}}%
}%<<<
\mynewdef[\protected\long]\myoarg@darg#1#2#3%>>>
{%
\long\def\myoarg@darg@##1#1##2#2{##1{##2}}%
\@ifnextchar#1{\myoarg@darg@{#3}}{#3{\my@mark}}%
}%<<<
\mynewdef[\protected\long]\myoarg@Darg#1#2#3#4%>>>
{%
\long\def\myoarg@darg@##1#1##2#2{##1{##2}}%
\@ifnextchar#1{\myoarg@darg@{#4}}{#4{#3}}%
}%<<<
% <<<
% macros for string comparison >>>
\begingroup\def\:{\endgroup\let\my@sptoken= }\:
\mynewdef\my@mark{\my@mark@if@you@see@this@report@it}
\mynewdef\my@stop{\my@stop@if@you@see@this@report@it}
\mynewdef\myparser@final{final}
\mynewdef\myparser@noarg{noarg}
\mynewdef\myparser@blankspace{blank space}
% <<<
\mynewdef[\protected]\myparserdef#1#2%>>>
{%
\def\myparserdef@twoargs{{#1}{#2}}%
\myparserdef@a
}%<<<
\mynewdef[\protected]\myparserdef@a%>>>
{%
\futurelet\myparserdef@arg\myparserdef@b
}%<<<
\mynewdef\myparserdef@b%>>>
{%
\ifx\myparserdef@arg\my@sptoken
\myfi@BTb
\fi
\@secondoftwo
{\myparserdef@gobble@space}
{\myparserdef@c}%
}%<<<
\mynewdef[\protected]\myparserdef@gobble@space%>>>
{%
\afterassignment\myparserdef@a
\let\myparserdef@arg= % space after = to get spaces, too
}%<<<
\mynewdef[\protected]\myparserdef@c#1%>>>
{%
\ifx\myparserdef@arg\bgroup
\myfi@BTb
\fi
\@secondoftwo
{%
\def\myparserdef@arg{#1}%
\ifx\myparser@blankspace\myparserdef@arg
\myfi@BTb
\fi
\@secondoftwo
{\expandafter\myparserdef@d\myparserdef@twoargs{blank space \space}}
{\expandafter\myparserdef@d\myparserdef@twoargs{#1}}%
}
{\expandafter\myparserdef@d\myparserdef@twoargs{\meaning\myparserdef@arg}}%
}%<<<
\mynewdef[\long]\myparserdef@d#1#2#3%>>>
{%
\myoarg@oarg{\myparserdef@e{#1}{#2}{#3}}%
}%<<<
\mynewdef[\protected\long]\myparserdef@e#1#2#3#4#5%>>>
{%
% if that is the first rule for the parser, define space to be a no-op
\myifcsundefTF{\myparser@name{#1}}%
{%
\expandafter\def
\csname \myparser@name{#1} all blank space \space\space type\endcsname
{noarg}%
\expandafter\def
\csname \myparser@name{#1} all blank space \space\space code\endcsname
{\myparser@getnexttoken}%
% use a macro as a flag that there is at least one parser rule defined
\expandafter\def\csname\myparser@name{#1}\endcsname{}%
}
{}%
% check whether the optional argument was used
\my@ifmark{#4}
{%
% use a macro as flag that there is a rule for the specified state
\expandafter\def\csname \myparser@name{#1} #2 #3 type\endcsname{noarg}%
\def\myparserdef@arg@count{0}%
}
{%
\edef\myparserdef@arg@count
{\the\numexpr\myparserdef@argcount#4\my@mark}%
\ifnum\myparserdef@arg@count>9%
\PackageError{my}{Too many arguments for parser rule}
{A maximum of 9 parameters is supported.}%
\else
\ifnum\myparserdef@arg@count=0%
\expandafter\def\csname \myparser@name{#1} #2 #3 type\endcsname
{noarg}%
\else
\expandafter\def\csname \myparser@name{#1} #2 #3 type\endcsname{#4}%
\fi
\fi
}%
% define what the code does
\expandafter\def\csname \myparser@name{#1} #2 #3 code\endcsname{}%
\expandafter\renewcommand\csname \myparser@name{#1} #2 #3 code\endcsname
[\myparserdef@arg@count]{#5\myparser@getnexttoken}%
}%<<<
\mynewdef\myparserdef@argcount#1%>>>
{%
\ifx\my@mark#1%
\myfi@An
\else
\myfi@By
\fi
{\csname myparserdef@argcount@#1\endcsname}%
}%<<<
\mynewdef\myparserdef@argcount@m%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@o%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@O#1%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@d#1#2%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@D#1#2#3%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@r#1%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparserdef@argcount@t#1%>>>
{%
+1%
\myparserdef@argcount
}%<<<
\mynewdef\myparser@name#1%>>>
{%
myparser #1%
}%<<<
\mynewdef\myparser@rule#1%>>>
{%
\myparser@current\space #1\space \meaning\myparser@token
}%<<<
\mynewdef\myparser@type#1%>>>
{%
\myparser@rule{#1} type%
}%<<<
\mynewdef\myparser@code#1%>>>
{%
\myparser@rule{#1} code%
}%<<<
\mynewdef[\protected]\myparserrun#1%>>>
{%
\myifcsundefTF{\myparser@name{#1}}
{\PackageError{my}{No parser named '#1' defined}{}}
{%
\edef\myparser@current{\myparser@name{#1}}%
\def\myparser@usersname{#1}%
\def\myparser@state{initial}%
\myparser@getnexttoken
}%
}%<<<
\mynewdef[\protected]\myparser@getnexttoken%>>>
{%
\ifx\myparser@state\myparser@final
\myfi@An
\else
\myfi@By
\fi
{%
\afterassignment\myparser@getnexttoken@a
\let\myparser@token= % space after = to get spaces, too
}%
}%<<<
\mynewdef[\protected]\myparser@getnexttoken@a%>>>
{%
\myifcsundefTF{\myparser@type{\myparser@state}}
{%
\myifcsundefTF{\myparser@type{all}}
{%
\PackageError{my}
{%
No rule for parser '\myparser@usersname' in state
'\myparser@state' for '\meaning\myparser@token'.
Ignoring token%
}
{}%
\myparser@getnexttoken
}
{\myparser@handle{all}}%
}
{\myparser@handle{\myparser@state}}%
}%<<<
\mynewdef[\protected]\myparser@handle#1%>>>
{%
\expandafter\ifx\csname\myparser@type{#1}\endcsname\myparser@noarg
\myfi@BTb
\fi
\@secondoftwo
{\csname\myparser@code{#1}\endcsname}
{\myparser@handle@args{#1}}%
}%<<<
\mynewdef[\protected]\myparserstate#1%>>>
{%
\def\myparser@state{#1}%
}%<<<
\mynewdef[\protected]\myparser@handle@args#1%>>>
{%
\def\myparser@stateorall{#1}%
\expandafter\expandafter\expandafter\myparser@handle@args@a
\csname\myparser@type{#1}\endcsname\my@mark\my@stop
}%<<<
\mynewdef\myparser@handle@args@a%>>>
{%
\myparser@handle@args@b{}%
}%<<<
\mynewdef[\long]\myparser@handle@args@b#1#2#3\my@stop%>>>
{%
\my@ifmark{#2}
{\csname\myparser@code{\myparser@stateorall}\endcsname#1}
{%
\myifcsundefTF{myparser@handle@args@#2}
{\PackageError{my}{Unknown argument type '#2'}{}}
{\csname myparser@handle@args@#2\endcsname{#1}{#3}}%
}%
}%<<<
\mynewdef[\long]\myparser@handle@args@c#1#2%>>>
{%
\my@ifmark{#2}
{\csname\myparser@code{\myparser@stateorall}\endcsname#1}
{\myparser@handle@args@b{#1}#2\my@stop}%
}%<<<
\mynewdef[\long]\myparser@handle@args@m#1#2#3%>>>
{%
\myparser@handle@args@c{#1{#3}}{#2}%
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@o#1#2%>>>
{%
\myoarg@oarg{\myparser@handle@args@m{#1}{#2}}%
}%<<<
\mynewdef[\long]\myparser@handle@args@O#1#2%>>>
{%
\myparser@handle@args@O@{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@O@#1#2#3\my@stop%>>>
{%
\myoarg@Oarg{#2}{\myparser@handle@args@m{#1}{#3}}%
}%<<<
\mynewdef[\long]\myparser@handle@args@d#1#2%>>>
{%
\myparser@handle@args@d@{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@d@#1#2#3#4\my@stop%>>>
{%
\myoarg@darg{#2}{#3}{\myparser@handle@args@m{#1}{#4}}%
}%<<<
\mynewdef[\long]\myparser@handle@args@D#1#2%>>>
{%
\myparser@handle@args@D@{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@D@#1#2#3#4#5\my@stop%>>>
{%
\myoarg@Darg{#2}{#3}{#4}{\myparser@handle@args@m{#1}{#5}}%
}%<<<
\mynewdef[\long]\myparser@handle@args@r#1#2%>>>
{%
\myparser@handle@args@r@a{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@r@a#1#2#3\my@stop%>>>
{%
\def\myparser@handle@args@r@b##1#2{\myparser@handle@args@c{#1{##1}}{#3}}%
\myparser@handle@args@r@b
}%<<<
\mynewdef[\long]\myparser@handle@args@t#1#2%>>>
{%
\myparser@handle@args@t@a{#1}#2\my@stop
}%<<<
\mynewdef[\protected\long]\myparser@handle@args@t@a#1#2#3\my@stop%>>>
{%
\@ifnextchar#2%
{\@firstoftwo{\myparser@handle@args@c{#1{\my@mark}}{#3}}}
{\myparser@handle@args@c{#1{\my@stop}}{#3}}%
}%<<<
\let\myIfNoValueTF\my@ifmark
\let\myIfBooleanTF\my@ifmark
\makeatother
% <<<

% the sheet path parser >>>
\makeatletter
\def\insertpath#1{\edef\sheetpath@{\unexpanded\expandafter{\sheetpath@#1}}}
\def\sheetpathcurve#1#2#3#4%
{%
to[out=0,in=180] ++({.5*\sheetlength},{#1*\sheetheight})
\myIfBooleanTF{#3}{}{node[scale=\sheetdotsize,inner sep=0pt]{.}}
\myIfNoValueTF{#4}{}{coordinate({#4})}
to[out=0,in=180] ++({.5*\sheetlength},{#2*\sheetheight})
}
\def\outputpath{\draw\sheetpath@;}
\def\sheet{\def\sheetpath@{}\myparserrun{sheet path}}
\makeatother
\myparserdef{sheet path}{initial}{the character (}[r,r)]
{\insertpath{({#1},{#2*\sheetheight})}\myparserstate{started}}
\myparserdef{sheet path}{initial}{the character =}
{\insertpath{(0,0)--++(\sheetlength,0)}\myparserstate{started}}
\myparserdef{sheet path}{started}{the character (}[r)]
{\insertpath{coordinate({#1})}}
\myparserdef{sheet path}{started}{the character =}
{\insertpath{--++(\sheetlength,0)}}
\myparserdef{sheet path}{started}{the letter u}[txd()]
{\insertpath{\sheetpathcurve{.5}{-.5}{#1}{#2}}}
\myparserdef{sheet path}{started}{the letter U}[txd()]
{\insertpath{\sheetpathcurve{1}{-1}{#1}{#2}}}
\myparserdef{sheet path}{started}{the letter d}[txd()]
{\insertpath{\sheetpathcurve{-.5}{.5}{#1}{#2}}}
\myparserdef{sheet path}{started}{the letter D}[txd()]
{\insertpath{\sheetpathcurve{-1}{1}{#1}{#2}}}
% making '.' a no-op, that way we can use it to end the search for an optional
% argument for instance
\myparserdef{sheet path}{started} .{}
\myparserdef{sheet path}{started}{the letter x}[d()]
{%
\myIfNoValueTF{#1}
{%
\insertpath
{--node[scale=\sheetdotsize,inner sep=0pt]{.}++(\sheetlength,0)}%
}
{%
\insertpath
{%
--node[scale=\sheetdotsize,inner sep=0pt]{.}
coordinate({#1})
++(\sheetlength,0)
}%
}%
}
\myparserdef{sheet path}{all}{the character ;}{\myparserstate{final}\outputpath}
% <<<

% sheet height and sheet length >>>
\pgfset
{
,sheet height/.store in=\sheetheight
,sheet height=1
,sheet length/.store in=\sheetlength
,sheet length=1
,sheet dot size/.store in=\sheetdotsize
,sheet dot size=4
}
%<<<

\begin{document}
\begin{tikzpicture}
\begin{scope}[thick, sheet height=0.75]
\sheet          ===     ===d(sp1)=;
\sheet (0,-1)   ==d(sp2)===ux    =;
\sheet (0,-2)   ==ux    ===Dx    =;
\sheet (0,-3)   ===     ====     =;
\sheet (0,-4)   ===     ===U(sp3)=;
\sheet (0,-5.5) ==x(b1) ===x(b2) =;
\end{scope}
\draw[dashed]
(b2) -- (sp1)
(b1) -- (sp2)
;
\end{tikzpicture}
\end{document}


• Wow ! This is a huge work. I'm impressed (like always I'm by the texperts ;)). – Kpym Mar 25 at 20:52
• @Kpym there are many parts of the code that are not really necessary, so I could've shortened it (especially by using some LaTeX3 packages like xparse). But thanks. – Skillmon Mar 26 at 9:47

This is a proof of concept how to use turtle library to define the following "moves" :

• up make an up bump
• up* make an up bump with (red) dot
• dn make a down bump
• dn* make a down bump with (red) dot
• * put a (red) dot
\documentclass[tikz,border=7pt]{standalone}
\usetikzlibrary{turtle}
\tikzset{
turtle/.cd,
how/.style={out=0,in=180},
home/.append style=right,
*/.style={/tikz/insert path={node[red,scale=4]{.}}},
forward/.default=1.4142135,
up/.style={left=45,forward,right,forward,left=45},
up*/.style={left=45,forward,*,right,forward,left=45},
dn/.style={right=45,forward,left,forward,right=45},
dn*/.style={right=45,forward,*,left,forward,right=45},
next/.style={/tikz/yshift=-1cm}
}

\begin{document}
\tikz
\draw[thick]
[turtle={home,fd,fd,fd,fd,fd,fd,dn,fd},next,next]
[turtle={home,fd,fd,dn,fd,fd,up*,fd},next,next]
[turtle={home,fd,fd,up*,fd,fd,dn,fd},next]
[turtle={home,fd,fd,fd,fd,fd,fd,fd,fd,fd},next]
[turtle={home,fd,fd,fd,fd,fd,fd,up*,fd}]
;
\end{document}


This is a proof of concept how to use pics to define a singularity and then connect it to other ones.

The singularity pic can be used like this :

And a fool code example is here :

\documentclass[tikz,border=7pt]{standalone}
\usetikzlibrary{turtle}
\tikzset{
fibers/.store in=\fibers,
fiber/.pic={
\path[pic actions] foreach \i in {0,...,\fibers} {(0,\i) coordinate (-\i) (0,0) -- (0,\fibers)};
},
singularity/.pic={
\draw[#1,yscale=.5]
foreach \i in \singularityinputs {(-1,\i) coordinate (-in\i) (-1,\i) edge[out=0,in=180] (0,0)}
foreach \o in \singularityoutputs {(1,\o) coordinate (-out\o) (0,0) edge[out=0,in=180] (1,\o)}
(0,0) node[fill,circle,inner sep=0,minimum width=1mm]{}
;
},
input/.store in=\singularityinputs,
output/.store in=\singularityoutputs,
sheets/.style = {input={#1},output={#1}}
}

\begin{document}
\tikz
\draw[thick,fibers=5]
% define the fibers
(0,0) pic(start){fiber}
-- (2,0) pic[draw,dashed](a){fiber} pic[fill=red,sheets={}]{singularity}
-- (5,0) pic[draw,dashed](b){fiber} pic[fill=red,sheets={}]{singularity}
-- (7,0) pic(end){fiber}
% put the singularities
(a-3) pic[fill=red,sheets={-1,1}](A){singularity}
(b-2) pic[fill=red,sheets={-1,0,1}](B){singularity}
(b-4) pic[fill=red,sheets={-1,1}](C){singularity}
% draw the sheets (by connecting singularities)
(A-in-1) -- (A-in-1-|start-0) (A-in1) -- (A-in1-|start-0)
(A-out1) -- (C-in-1) (A-out-1) -- (B-in1)
(B-in0) -- (B-in0-|start-0) (B-in-1) -- (B-in-1-|start-0)
(B-out-1) -- (B-out-1-|end-0) (B-out0) -- (B-out0-|end-0) (B-out1) -- (B-out1-|end-0)
(C-in1) -- (C-in1-|start-0)
(C-out1) -- (C-out1-|end-0) (C-out-1) -- (C-out-1-|end-0)
;
\end{document}