%% \iffalse %% Copyright (C) 2024 Jerome Plut %% %% This work may be distributed and/or modified under the %% conditions of the LaTeX Project Public License, either version 1.3 %% of this license or (at your option) any later version. %% The latest version of this license is in %% https://www.latex-project.org/lppl.txt %% and version 1.3c or later is part of all distributions of LaTeX %% version 2008-05-04 or later. %% %% This work has the LPPL maintenance status "maintained". %% %% The Current Maintainer of this work is Jerome Plut. %% %% This work consists of the files `euclidean-lattice.ins` %% and `euclidean-lattice.dtx`, %% as well as the derived file `euclidean-lattice.sty`. %% \fi % \iffalse %<*driver> \ProvidesFile{euclidean-lattice.dtx} % % %<*driver> \documentclass{ltxdoc} \usepackage{color} \usepackage{euclidean-lattice} \usepackage[listings]{tcolorbox} \tcbuselibrary{skins} \usepackage[margin=25mm]{geometry} \usepackage{amsmath,amsfonts,amssymb} \usepackage{titling} \setlength{\droptitle}{-30mm} \sloppy \definecolor{blue1}{RGB}{0,53,130} \definecolor{red1}{RGB}{218,41,28} \definecolor{green1}{RGB}{0,150,59} \definecolor{purple1}{RGB}{114,36,108} \definecolor{cyan1}{RGB}{242,249,245} \definecolor{cyan2}{RGB}{28,133,115} \parindent 0pt \parskip 1ex \EnableCrossrefs \CodelineIndex \RecordChanges \newtcblisting{example}{% listing side text,tikz lower={scale=.5}, listing options={basicstyle=\ttfamily\fontsize{8pt}{9pt}\selectfont, breaklines=true,language=tex,commentstyle=\color{cyan2}\itshape}, sharp corners,boxrule=.4pt,righthand width=55mm,boxsep=0mm,left=2mm, sidebyside gap=2mm, skin=bicolor,colback=cyan1,colframe=black,colbacklower=white, } \makeatletter \def\makecompact#1{\g@addto@macro#1{% \setlength{\itemsep}{\z@}\setlength{\parsep}{\z@}% \setlength{\topsep}{\z@}\setlength{\partopsep}{\z@}% }}\makecompact\itemize \makecompact\enumerate \makeatother \let\labelitemi\textendash \begin{document} \DocInput{euclidean-lattice.dtx} \PrintChanges \PrintIndex \end{document} % %<*doc> \fi \GetFileInfo{euclidean-lattice.sty} \title{The \textsf{euclidean-lattice} package} \author{J\'er\^ome Pl\^ut \textlangle\texttt{jerome.plut@cyber.gouv.fr}\textrangle} \date{\textsf{euclidean-lattice}~\fileversion, dated~\filedate} \maketitle This package provides a simple and efficient way of drawing \textsf{TikZ} pictures of two-dimensional Euclidean lattices. \section{Usage} \subsection{The \texttt{\textbackslash lattice} command} \DescribeMacro{\lattice} {\color{red1}|\lattice|}% {\color{green1}|<|\textit{overlay specification}|>|}% {\color{blue1}|[|\textit{options...}|]|}% |(|$a$|,|$b$|)(|$c$|,|$d$|);| This commands draws a part of the two-dimensional lattice generated by the vectors $(a,b)$ and $(c,d)$. The {\color{green1}\textit{overlay specification}} is optional and follows~\textsf{beamer} syntax. It will only work if the \textsf{beamer} class is loaded. The {\color{blue1}\textit{options}} enable customizing how the lattice is displayed. They follow the standard \textsf{TikZ/PGF} key-value interface. The following options are available. \DescribeMacro{x} \DescribeMacro{y} {{\color{blue1}\texttt{x=}$x_1$|:|$x_2$} and {\color{blue1}\texttt{y=}$y_1$|:|$y_2$}} (default value |x=-2:2|,|y=-2:2|). These two options specify a bounding box which determines which part of the lattice is drawn. As a shortcut, passing the value |x=|$x_1$ is equivalent to |x=|$-x_1$|:|$x_1$, and likewise for~|y|. \begin{example} \draw[help lines,gray!25] (-5,0) grid (5,2); \lattice[x=5,y=0:2](3,0)(1,1); \node[inner sep=1.5pt,circle,fill=red] at (0,0){}; \end{example} \DescribeMacro{grid} {{\color{blue1}\texttt{grid=}\textit{options...}}} This option specifies how to draw the grid generated by the two given lattice vectors. It may take either a single value (generally a color name), or several values grouped in braces. Note that this grid depends on the basis vectors and not only on the lattice; see the example below. \begin{example} \lattice[y=0:2,x=0:5,grid=gray](2,-1)(1,1); % Same lattice, different basis: \lattice[y=0:2,x=6:11,grid={yellow,very thick}] (3,0)(4,1); \end{example} \DescribeMacro{bounding box} {{\color{blue1}\texttt{bounding box=}\textit{options...}}} This option specifies how to draw the bounding box given by the |x| and |y| options. It may take either a single value (generally a color name), or several values grouped in braces. \begin{example} \lattice[bounding box=green,x=-5:5,y=0:2](3,0)(1,1); \end{example} \DescribeMacro{each point} {{\color{blue1}\texttt{each point/.code n args=\{5\}\{}% \textit{code...}\texttt{\}}}} This option contains contains the code to execute for each lattice point. This code is called with the five following parameters: \begin{itemize} \item (|#1|) the parsed lattice node options, \item (|#2|,|#3|) the $(x,y)$ coordinates of the current lattice point (as \textsf{TikZ} canvas coordinates); \item (|#4|,|#5|) the coordinates of the same point relative to the provided lattice basis (as integers). \end{itemize} Note that, due to this parameter being expanded inside a macro, all the |#| signs must be doubled in its definition, as in the following example. \begin{example} \lattice[x=-5:5,y=0:2,each point/.code n args= {5}{\node[##1,fill=cyan!10] at (##2,##3){(##4,##5)};}] (3,0)(1,1); \end{example} \textbf{Other options} Any remaining options are directly passed to the |\node| calls for each lattice point. \begin{example} \lattice[red,rectangle,inner sep=2pt,x=-5:5,y=0:2] (3,0)(1,1); \end{example} \subsection{Configuring default behaviour} \DescribeMacro{x} \DescribeMacro{y} \DescribeMacro{grid} \DescribeMacro{bounding box} \DescribeMacro{each point} The |/lattice/x|, |/lattice/y|, |/lattice/grid|, |/lattice/bounding box|, |/lattice/each point| PGF keys contain the default parameters for the correspondingly-named options. Note that the |#| signs must \emph{not} be doubled in |/lattice/each point|. \begin{example} \pgfqkeys{/lattice}{x=-5:5,y=-.1:2.1, grid={blue!10,very thick}, bounding box={red,fill=gray!5}, each point/.code n args={5}{% \node[#1,fill=cyan!10,rectangle] at (#2,#3) {\tiny (#4,#5)};}} \lattice[grid,bounding box](3,0)(1,1); \end{example} \DescribeMacro{node} The |/lattice/node| key contains the default parameters for the |\node| call for each point. Equivalently, these are the default options passed to each |\lattice| call. \begin{example} \pgfkeys{/lattice/node={fill=yellow!25,draw=red}} \lattice[x=-5:5,y=0:2](3,0)(1,1); \end{example} \subsection{Exactness and limitations} This command only outputs lattices containing the origin. An offset lattice may still be obtained by using the PGF transformation mechanism and correspondingly adjusting the |x|,|y| bounding box parameters: \begin{example} \begin{scope}\pgftransformshift{\pgfpoint{1cm}{0}} \lattice[x=-3:1,red,rectangle](2,0)(1,1); \end{scope} \lattice(2,0)(1,1); \end{example} This command might be executed a large number of times in a single document, for example in the case of a \textsf{beamer} presentation with a large number of overlays. This means that we need to be careful to make it efficient. Therefore, all computations are performed using \TeX\ registers. Since these registers only implement 32-bit integer arithmetic, we use fixed-point arithmetic with 16-bit offset for numeric computations (which include matrix inversion). This implies some precision loss and enforces some numeric limits. The use of fixed-point arithmetic means that the products appearing in the determinant of the lattice must not exceed $2^{15}$. In particular, this package should work as long as all the coordinates (in absolute value) do not exceed~$128$, which should cover most typical scales of \textsf{TikZ} pictures. We compensate for the precision loss by sampling a bit too many lattice points (relative to the required bounding box) and then filtering \emph{a posteriori} to ensure that all points are in the bounding box. This last step should remain exact, at least as soon as the coordinates of the lattice vectors are integers (or integer multiples of $2^{-8}$). Therefore, we expect that in most cases, the command will exactly output the lattice points inside the bounding box. If this were to fail, however, as a last-resort option, enlarging the bounding box by a small number in all directions should ensure that all lattice points appear. %\iffalse % %\fi % \section{Implementation} % % \paragraph{Initialization.} % We obviously depend on~\textsf{tikz} % (and more precisely on~\textsf{pgfkeys}). % \begin{macrocode} \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{euclidean-lattice}[2024-07-26 Euclidean~lattices 1.0] \RequirePackage{tikz} % \end{macrocode} % We use fixed-point arithmetic in the 31-bit registers supported by % \TeX, using 16 bits for offset. % % TODO: we could slightly enhance the precision of computations by % correctly rounding the last bit (instead of truncating). % Depending on the value's sign, % |\advance XXX by .5\la@unit| should do the trick. % % This means that we represent a real number~$x$ % using the integer $f(x) = B x$, where $B = 2^{16}$. % To multiply two such integers, we note that % $f(xy) = B x y = f(x) f(y)/B$, % which we compute as $(f(x)/\beta) \cdot (f(y)/\beta)$, % where $\beta = \sqrt{B} = 2^{8}$. % We store this value in the register |\la@unit|. % \begin{macrocode} \newdimen\la@unit \la@unit=256sp \def\lattice@mul#1#2{% writes #1*#2 in #1 \@tempdimc=#2 \divide\@tempdimc\la@unit \divide #1\la@unit \multiply #1\@tempdimc}% % \end{macrocode} % We perform division using % $f(x/y) = B x/y = B f(x)/f(y)$, % which we compute as $\frac{\beta f(x)}{f(y)/\beta}$. % \begin{macrocode} \def\lattice@div#1#2{% \@tempdimc=#2\divide\@tempdimc\la@unit \multiply #1\la@unit \divide#1\@tempdimc}% \def\lattice@invert#1{% writes 1/#1 in #1 \@tempdimc=#1 \divide\@tempdimc\la@unit #1=256\p@ \divide #1\@tempdimc}% % \end{macrocode} % The next macro is our main enumeration routine. % It enumerates all the point in the lattice~$\Lambda$ generated % by the vectors $(a=|#1|,b=|#2|)$ and $(c=|#3|,d=|#4|)$ % inside the box~$R$ delimited by the registers % $[|\la@Ax|,|\la@Bx|] \times [|\la@Ay|,|\la@By|]$. % On each such point, it invokes the macro % |\lattice@donode|. % % Since $\Lambda = B \cdot \mathbb{Z}^2$, % instead of enumerating $S = \Lambda \cap R$ % we enumerate $B^{-1} S = \mathbb{Z}^2 \cap (B^{-1} R)$. % We use $(x,y)$ coordinates to describe elements of~$S$ % and $(w,z)$ coordinates to describes elements of~$B^{-1} S$. % % We declare several numeric registers, all prefixed with |\la@|: % \begin{itemize} % \item $(a,b)$ and $(c,d)$ are the vectors of the lattice basis~$B$; % \item $R := [Ax, Bx] \times [Ay, By]$ is the bounding box for the drawing; % \item $U := (Ux, Uy)$ and $V := (Vx, Vy)$ are the side vectors % of the parallelogram $P := B^{-1} \cdot R$, % and~$C := (Cx, Cy)$~is its center; % \item $sM < sL$ are the slopes of the vectors $U$ and~$V$; % \item $wA, wB, wM$ are the horizontal parameters for enumerating % the parallelogram; % \item $zA, zB, zC$ are the vertical parameters for enumerating the % parallelogram; % \item $D$ plays a double role, first as the (inverse) determinant of % the lattice, and then as a vertical enumeration parameter. % \end{itemize} % \begin{macrocode} \newdimen\la@a \newdimen\la@b \newdimen\la@c \newdimen\la@d \newdimen\la@Ax\newdimen\la@Bx\newdimen\la@Ay\newdimen\la@By \newdimen\la@D \newdimen\la@Ux\newdimen\la@Uy \newdimen\la@Vx\newdimen\la@Vy \newdimen\la@Cx\newdimen\la@Cy \newdimen\la@sL \newdimen\la@sM \newdimen\la@zA \newdimen\la@zB \newdimen\la@zC \newdimen\la@wA \newdimen\la@wB \newcount\la@wM % \end{macrocode} % \begin{macrocode} \def\lattice@enumerate#1#2#3#4{% \la@a=#1\p@ \la@b=#2\p@ \la@c=#3\p@ \la@d=#4\p@ % \end{macrocode} % First we store the \textrm{inverse} of the determinant in |\la@D|. % \begin{macrocode} \la@D=\la@a \lattice@mul\la@D\la@d \@tempdima=\la@b \lattice@mul\@tempdima\la@c \advance\la@D -\@tempdima \lattice@invert\la@D % \end{macrocode} % Compute the inverse image $B^{-1} R = P$. % We first compute the sides $U$, $V$ of the parallelogram, % multiplying by $-1$ if needed so that both $w$-coordinates are % $\geqslant 0$. % \begin{macrocode} \la@Ux=\la@Ax \advance\la@Ux-\la@Bx \lattice@mul\la@Ux \la@D \la@Uy=-\la@Ux \lattice@mul\la@Ux \la@d \ifdim\la@Ux <\z@ \multiply\la@Ux -1 \multiply\la@Uy -1 \fi \lattice@mul\la@Uy\la@b \la@Vx=\la@By \advance\la@Vx-\la@Ay \lattice@mul\la@Vx\la@D \la@Vy=-\la@Vx \lattice@mul\la@Vx\la@c \ifdim\la@Vx<\z@ \multiply\la@Vx -1 \multiply\la@Vy -1\fi \lattice@mul\la@Vy \la@a % \end{macrocode} % We ensure that $\det(U,V) > 0$, swapping both vectors if needed. % Since we only need the sign of this determinant, we scale down the % values to help prevent a numeric overflow. % \begin{macrocode} \@tempdima \la@Ux \@tempdimb \la@Vx \ifdim\@tempdima<100\p@\else \ifdim\@tempdimb<100\p@\else \divide\@tempdima 1024 \divide\@tempdimb 1024\fi\fi \lattice@mul\@tempdima\la@Vy \lattice@mul\@tempdimb\la@Uy \ifdim \@tempdimb>\@tempdima \@tempdimc=\la@Ux \la@Ux=\la@Vx \la@Vx=\@tempdimc \@tempdimc=\la@Uy \la@Uy=\la@Vy \la@Vy=\@tempdimc \fi % \end{macrocode} % Compute the center of the parallelogram. % Note that we also use |\la@sL|, |\la@sM| (not yet needed) % as temporaries here. % \begin{macrocode} \la@sL=\la@Ax \advance\la@sL\la@Bx \divide\la@sL 2 \la@sM=\la@Ay \advance\la@sM\la@By \divide\la@sM 2 \la@Cx=\la@sL \lattice@mul\la@Cx\la@d \@tempdima=\la@sM \lattice@mul\@tempdima\la@c \advance\la@Cx -\@tempdima \la@Cy=\la@sM \lattice@mul\la@Cy\la@a \@tempdima=\la@sL \lattice@mul\@tempdima\la@b \advance\la@Cy -\@tempdima \lattice@mul \la@Cx\la@D \lattice@mul \la@Cy\la@D % \end{macrocode} % Compute the bounding-box in $(w,z)$ space. % Note that the |\la@wA|,|\la@wB| values will be re-used below % for enumerating all lattice points; % on the other hand, |\la@zA| and |\la@zB| are used here % as temporary values. % \begin{macrocode} \@tempdima\la@Ux \advance\@tempdima\la@Vx \la@wA=\la@Cx \advance\la@wA -.5\@tempdima \la@wB=\la@wA \advance\la@wB \@tempdima \@tempdima\la@Uy \ifdim\@tempdima<\z@ \multiply\@tempdima -1\fi \ifdim\la@Vy<\z@ \advance\@tempdima -\la@Vy \else\advance\@tempdima\la@Vy\fi \la@zA=\la@Cy \advance\la@zA -.5\@tempdima \la@zB=\la@zA \advance\la@zB \@tempdima % \end{macrocode} % Draw the grid if the options require it. % \begin{macrocode} \ifx\lattice@grid\@empty\else \begin{scope}% \clip (\strip@pt\la@Ax,\strip@pt\la@Ay) rectangle (\strip@pt\la@Bx,\strip@pt\la@By); \pgftransformcm{\strip@pt\la@a}{\strip@pt\la@b}% {\strip@pt\la@c}{\strip@pt\la@d}{\pgfpointorigin}% \expandafter\draw\expandafter[\lattice@grid] (\strip@pt\la@wA,\strip@pt\la@zA) grid (\strip@pt\la@wB,\strip@pt\la@zB); \end{scope}\fi % \end{macrocode} % Compute the last values required to enumerate over $\mathbb{Z}^2 \cap P$. % We represent the parallelogram~$P$ as the intersection of four half-planes. % Let $A_1=(w_1,z_1)$ and $A_2=(w_2,z_2)$ be % the left-most and right-most vertices of~$P$, % and $\lambda, \mu$ be the slopes of the sides % (the condition $\det(U,V) > 0$ guarantees that $\lambda < \mu$). % % The two sides originating from~$A_i (i=1,2)$ have equations % \[ z=z_i+\lambda(w-w_i), \qquad z=z_i+\mu(w-w_i); \] % By defining $z_3 = z_2 - \lambda (w_2 - w_1)$ and $z_4=z_2-\mu(w_2-w_1)$, % we may rewrite both equations from~$A_2$ as: % \[ z= z_3 + \mu(w-w_1), % \qquad z = z_4 + \lambda(w-w_1) \] % The parallelogram~$P$ is then defined by the following inequations, % where we write $t = w - w_1$: % \[ \max(z_3+\mu t, z_1+\lambda t) \;\leqslant\; z \;\leqslant\; % \min(z_1+\mu t, z_4+\lambda t). \] % For any given value of~$w$ we store the value of these four affine % functions in the variables |\la@zB|, |\la@zA|, |\la@D| (not a typo), % |\la@zC|, in this order. % When increasing the value of~$w$ we only need to increase the values % of these four registers respectively by $\mu$, $\lambda$, $\mu$, % $\lambda$. % % First we compute the values $z_2 = z_C + \frac 12(z_U+z_V)$ % and~$w_2 - w_1 = w_U + w_V$. % \begin{macrocode} \@tempdima=\la@Uy \advance\@tempdima\la@Vy \@tempdimb=\la@Cy \advance\@tempdimb .5\@tempdima % z2 \@tempdima=\la@Ux \advance\@tempdima\la@Vx % w_2-w_1 = w(U+V) % \end{macrocode} % Then we compute the slopes $\lambda = |\la@sL|$ and $\mu = |\la@sM|$. % A Special case happens when either $w_U$ or $w_V$ is zero. % In the first case, $\lambda = -\infty$. % This means that the comparisons with |\la@zA| and |\la@zC| must be % ignored. % \begin{macrocode} \ifdim\la@Ux=\z@ \la@sL=\z@ \la@zC=\maxdimen \la@zA=-\maxdimen \else \la@sL=\la@Uy \lattice@div\la@sL\la@Ux \la@zC=-\@tempdima \lattice@mul\la@zC\la@sL \advance\la@zC \@tempdimb \la@zA=\@tempdimb \advance\la@zA -\la@Uy \advance\la@zA -\la@Vy \fi % \end{macrocode} % Likewise, if $w_V = 0$ then $\mu = +\infty$ % and the comparisons with |\la@zB| and |\la@D| are ignored. % \begin{macrocode} \ifdim\la@Vx=\z@ \la@sM=\z@ \la@zB=-\maxdimen \la@D=\maxdimen \else \la@sM=\la@Vy \lattice@div\la@sM\la@Vx \la@zB=-\@tempdima \lattice@mul\la@zB\la@sM \advance\la@zB \@tempdimb \la@D=\@tempdimb \advance\la@D -\la@Uy \advance\la@D -\la@Vy \fi % \end{macrocode} % % At this point the enumerator for $B^{-1} S$ is fully computed; % it uses the registers |\la@sL|, |\la@sM| for the slopes, % |\la@wA|, |\la@wB| for the boundaries, % |\la@zA|, |\la@zB|, |\la@zC|, |\la@D| for the four affine functions, % as well as the original matrix~$B$ in |\la@a|, |\la@b|, |\la@c|, |\la@d| % (to recompute the points of~$\Lambda$). % % We re-use the freed registers in the following way: % \begin{itemize} % \item $(|\la@Vx|,|\la@Vy|)$ hold the $(w,z)$ coordinates % of the current lattice point (stored as integers); % \item $(|\la@Ux|,|\la@Uy|)$ hold the corresponding point % in $(x,y)$ space (stored as \textsf{dimen}s); % \item $(|\la@Cx|,|\la@Cy|)$ hold the $(x,y)$ coordinates % of the $(w,0)$ point, which is used for each column. % \end{itemize} % % Because the fixed-point arithmetic is imprecise, we might miss some % lattice points on the boundaries. We compensate for this by enlarging % the bounds ($-1$ for lower bounds, $+1$ for upper bounds) % and \emph{a posteriori} checking that all the computed points % are in the bounding box. % % Store in |\la@Vx| our starting $(w,z)$ coordinate, % which is the integer $\lceil|\la@wA|\rceil -1$. % Since \TeX\ knows only \emph{signed} division (groumpf), % computing the ceiling is sign-dependent. % \begin{macrocode} \la@Vx=\la@wA \advance\la@Vx\expandafter\ifdim\la@Vx>\z@ -1sp\else-\p@\fi \divide\la@Vx \p@ % \end{macrocode} % Compute the upper boundary $|\la@wB|=\lfloor|\la@wB|\rfloor+1$. % \begin{macrocode} \ifdim\la@wB<\z@ \advance\la@wB 1sp\else\advance\la@wB\p@\fi \divide\la@wB\p@ % \end{macrocode} % Compute the difference $t = \lceil w_1\rceil-w_1$ % and correspondingly advance the four affine functions. % \begin{macrocode} \@tempdima=\la@Vx \multiply\@tempdima\p@ \advance\@tempdima -\la@wA \@tempdimb=\@tempdima \lattice@mul\@tempdimb\la@sL \advance\la@zA\@tempdimb \advance\la@zC\@tempdimb \@tempdimb=\@tempdima \lattice@mul\@tempdimb\la@sM \advance\la@zB\@tempdimb \advance\la@D\@tempdimb % \end{macrocode} % Store in |\la@Cx|, |\la@Cy| the $(x,y)$ coordinates of the % lattice vector $w \cdot (a,b)$: % \begin{macrocode} \la@Cx=\la@a \multiply\la@Cx\la@Vx \la@Cy=\la@b \multiply\la@Cy\la@Vx % \end{macrocode} % The main $w$-loop starts here. % \begin{macrocode} \loop % \end{macrocode} % Compute the range of $z=|\la@Vy|$ for this value of $w=|\la@Vx|$. % The minimum value is $\max(|\la@zA|,|\la@zB|)-1$: % \begin{macrocode} \la@Vy=\la@zA \ifdim\la@Vy<\la@zB \la@Vy=\la@zB\fi \advance\la@Vy\expandafter\ifdim\la@Vy>\z@ -1sp\else-\p@\fi \divide\la@Vy\p@ % \end{macrocode} % The maximum value is $|\la@wM|=\min(|\la@zC|,|\la@D|)$: % \begin{macrocode} \la@wM=\la@zC \ifnum\la@wM>\la@D \la@wM=\la@D\fi \advance\la@wM\expandafter\ifnum\la@wM<\z@\@ne\else\p@\fi \divide\la@wM\p@ % \end{macrocode} % Compute the $(|\la@Ux|,|\la@Uy|)$ point in $(x,y)$ space: % \begin{macrocode} \la@Ux=\la@c \multiply\la@Ux\la@Vy \advance\la@Ux\la@Cx \la@Uy=\la@d \multiply\la@Uy\la@Vy \advance\la@Uy\la@Cy % \end{macrocode} % Inner ($z$) loop. Nested loops require grouping. % \begin{macrocode} {\loop % \end{macrocode} % If the point is inside the bounding box, we invoke |\lattice@donode|. % Note the use of |\expandafter| to ensure that the |\node| call inside % the PGF key |/lattice/each node| gets the expanded options list. % \begin{macrocode} \ifdim\la@Ux<\la@Ax\else\ifdim\la@Ux>\la@Bx\else \ifdim\la@Uy<\la@Ay\else\ifdim\la@Uy>\la@By\else \expandafter\lattice@donode\expandafter{\lattice@node}% \fi\fi\fi\fi % \end{macrocode} % End of the $z$ loop. We increase $z$ and correspondingly advance the vector. % \begin{macrocode} \ifnum\la@Vy<\la@wM \advance\la@Vy 1sp \advance\la@Ux\la@c \advance\la@Uy\la@d \repeat} % \end{macrocode} % End of the $w$ loop. We increase $w$ and correspondingly advance % the $w\cdot (a,b)$ vector as well as the four affine functions. % \begin{macrocode} \ifnum\la@Vx<\la@wB \advance\la@Vx 1sp \advance\la@Cx\la@a \advance\la@Cy\la@b \advance\la@zA \la@sL \advance\la@zC \la@sL \advance\la@zB \la@sM \advance\la@D \la@sM \repeat } % \end{macrocode} % This macro gets called for each found lattice point. % Its main job is to ensure that the node options (here |#1|) % are correctly expanded. % \begin{macrocode} \def\lattice@donode#1{% \pgfkeys{/lattice/arg/each point={#1}{\strip@pt\la@Ux}{\strip@pt\la@Uy}% {\number\la@Vx}{\number\la@Vy}}} % \end{macrocode} % The PGF keys used for parsing the arguments of the |\lattice| command; % they correspond to the options passed in square brackets. % \begin{macrocode} \pgfqkeys{/lattice/arg}{ x/.store in=\lattice@x, y/.store in=\lattice@y, grid/.store in=\lattice@grid, each point/.code n args={5}{\pgfkeys{/lattice/each point={#1}{#2}{#3}{#4}{#5}}}, bounding box/.store in=\lattice@bbox, .unknown/.code={% \expandafter\lattice@setnode\pgfkeyscurrentkey=#1\lattice@eov }} % \end{macrocode} % The PGF keys used for user configuration of the default values. % \begin{macrocode} \pgfqkeys{/lattice}{ x/.initial=-2:2,y/.initial=-2:2, node/.initial={circle,inner sep=1pt,draw=none,fill=black}, grid/.style={/lattice/arg/grid/.default={#1}}, grid={gray,very thin}, bounding box/.style={/lattice/arg/bounding box/.default={#1}}, bounding box={cyan,thin}, each point/.code n args={5}{\node[#1] at (#2,#3){};}, } % \end{macrocode} % This handles the passing of any unknown |\lattice| keys % down to the |\node| calls. % \begin{macrocode} \def\lattice@setnode/lattice/arg/#1\lattice@eov{% \edef\lattice@node{\expandafter\noexpand\lattice@node,#1}} % \end{macrocode} % This macro parses the bounding-box |x| and |y| coordinates, % replacing |x=10| by |x=-10:10|. % The |detokenize| trick was nicely provided by David Carlisle: % |https://tex.stackexchange.com/questions/724989/|. % \begin{macrocode} \def\lattice@getxy#1#2#3{% \def\m@gic##1:##2:##3\end{\ifx ##3: #2=##1\p@ #3=##2\p@\else #2=-##1\p@ #3=##1\p@\fi}% \expandafter\m@gic\detokenize{#1:}:\end } % \end{macrocode} % The following macros parse the possible combinations of overlays % and square-bracket options. % They are similar to the way |\tikz@command@path| is defined % in |tikz.code.tex|. % Just as in that case, the overlay is handled by a |\alt| call, % which will fail if \textsf{beamer} is not loaded. % \begin{macrocode} \def\lattice{\@ifnextchar<\lattice@I{\@ifnextchar[\lattice@II{\lattice@@[]}}} \def\lattice@I{\ifnum\the\catcode`\;=\active\relax \let\lattice@next\lattice@Iactive\else \let\lattice@next\lattice@Inormal\fi \lattice@next} \long\def\lattice@Inormal<#1>#2;{\alt<#1>{\lattice@I@#2;}{}} {\catcode`\;=\active \long\gdef\lattice@Iactive<#1>#2;{\alt<#1>{\lattice@I@#2;}{}} } \def\lattice@I@{\@ifnextchar[\lattice@@{\lattice@@[]}} \def\lattice@II[#1]{\@ifnextchar<{\lattice@IIi[#1]}{\lattice@@[#1]}} \def\lattice@IIi[#1]<#2>{\lattice@I<#2>[#1]} % \end{macrocode} % This macros parses the basis vectors and calls the main loop. % |#4| and |#7| are dummy parameters swallowing a possible space between % the parentheses and the final semicolon. % \begin{macrocode} \def\lattice@@[#1](#2,#3)#4(#5,#6)#7{ \let\lattice@grid\@empty% \let\lattice@bbox\@empty% \edef\lattice@x{\pgfkeysvalueof{/lattice/x}}% \edef\lattice@y{\pgfkeysvalueof{/lattice/y}}% \edef\lattice@node{\pgfkeysvalueof{/lattice/node}}% \pgfqkeys{/lattice/arg}{#1}% % \end{macrocode} % Read the bounding-box coordinates from the |x| and |y| keys: % \begin{macrocode} \expandafter\lattice@getxy\expandafter{\lattice@x}\la@Ax\la@Bx \expandafter\lattice@getxy\expandafter{\lattice@y}\la@Ay\la@By % \end{macrocode} % Draw the bounding-box if it was required by the options. % \begin{macrocode} \ifx\lattice@bbox\@empty\else \expandafter\draw\expandafter[\lattice@bbox] (\strip@pt\la@Ax,\strip@pt\la@Ay) rectangle (\strip@pt\la@Bx,\strip@pt\la@By); \fi % \end{macrocode} % Call the main loop. % \begin{macrocode} \lattice@enumerate{#2}{#3}{#5}{#6}% } % \end{macrocode} % % \Finale \endinput