diff options
Diffstat (limited to 'graphics/pgf/contrib/euclidean-lattice/euclidean-lattice.dtx')
-rw-r--r-- | graphics/pgf/contrib/euclidean-lattice/euclidean-lattice.dtx | 695 |
1 files changed, 695 insertions, 0 deletions
diff --git a/graphics/pgf/contrib/euclidean-lattice/euclidean-lattice.dtx b/graphics/pgf/contrib/euclidean-lattice/euclidean-lattice.dtx new file mode 100644 index 0000000000..bced9a5cb9 --- /dev/null +++ b/graphics/pgf/contrib/euclidean-lattice/euclidean-lattice.dtx @@ -0,0 +1,695 @@ +%% \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> +% +%<*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} +%</driver> +%<*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 +%</doc> +%\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 |