% \iffalse %% File: randomwalk.dtx Copyright (C) 2011-2018 Bruno Le Floch %% %% This work may be distributed and/or modified under the %% conditions of the LaTeX Project Public License, either version 1.3c %% of this license or (at your option) any later version. %% The latest version of this license is in %% http://www.latex-project.org/lppl.txt %% %% This work has the LPPL maintenance status 'maintained' %% and the current maintainer is Bruno Le Floch. %% %% This work consists of the files randomwalk.dtx and randomwalk.ins and %% derived files randomwalk.sty and randomwalk.pdf. %% ----------------------------------------------------------------------- % %<*driver|package> % %<*driver> \RequirePackage{expl3} \documentclass[full]{l3doc} \usepackage{randomwalk} \AtBeginDocument{\chgrand[seed = 411500]\rand} \usepackage{amsmath} \begin{document} \DocInput{randomwalk.dtx} \end{document} % % \fi % %^^A The date here also is in \ProvidesExplPackage, and in the copyright. % \def\fileversion{v0.6} % \def\filedate{2018/12/28} % % \title{The \pkg{randomwalk} package: \\ % customizable random walks\thanks{This file describes version % \fileversion, last revised \filedate.}} % \author{Bruno Le Floch\thanks{E-mail blflatex+randomwalk@gmail.com}} % \date{Released on \filedate} % % \maketitle % \tableofcontents % % \begin{documentation} % % \begin{abstract} % % The \pkg{randomwalk} package draws random walks. The % following parameters can be customized: % \begin{itemize} % \item The number of steps, of course. % \item The length of the steps, either a fixed length, or a length % taken uniformly at random from a given list. % \item The angle of each step, either taken uniformly at random % from a given list, or uniformly distributed between $0$~and % $360$ degrees. % \end{itemize} % % \end{abstract} % % % \section{How to use \pkg{randomwalk}} % % \newcommand{\examplei} % {\RandomWalk {number = 200, length = {4pt, 10pt}}} % \newcommand{\exampleii} % {\RandomWalk {number = 100, angles = {0,60,120,180,240,300}, degree}} % \newcommand{\exampleiii} % {\RandomWalk {number = 50, length = 1ex, angles = {0,24,48,-24,-48}, degree, angles-relative}} % \begin{function}{\RandomWalk} % The \pkg{randomwalk} package has a single user command: % \cs{RandomWalk}, which takes a list of key-value pairs as its % argument. A few examples are given in Figures~\ref{examplei}, \ref{exampleii}, and~\ref{exampleiii}: % \begin{quote}\ttfamily % \detokenize\expandafter{\examplei}\\ % \detokenize\expandafter{\exampleii}\\ % \detokenize\expandafter{\exampleiii} % \end{quote} % Here is a list of all the keys, and their meaning: % \begin{itemize} % \item \texttt{number}: the number of steps (default \(10\)) % \item \texttt{length}: the length of each step: either one dimension % (\emph{e.g.}, |1ex|), or a comma-separated list of dimensions % (\emph{e.g.}, |{2pt, 5pt}|), by default |10pt|. The length of each % step is a (uniformly distributed) random element in this set of % possible dimensions. % \item \texttt{angles}: the polar angle for each step: a % comma-separated list of angles, and each step takes a random angle % in the list. If this is not specified, then the angle is % uniformly distributed along the circle. % \item \texttt{degree} or \texttt{degrees}: specify that the angles % are given in degrees (by default, they are in radians). % \item \texttt{angles-relative}: instead of being absolute, the % angles are relative to the direction of the previous step. % \item \texttt{revert-random} (boolean, false by default): revert the % seed of the random number generator to its original value after % the random walk. % \end{itemize} % \end{function} % % \begin{figure} % \begin{center} % \framebox{\examplei} % \caption{\label{examplei}A \(200\) steps long % walk, where each step has one of two lengths: % \texttt{\detokenize\expandafter{\examplei}}} % \end{center} % \end{figure} % % \begin{figure} % \begin{center} % \framebox{\exampleii} % \caption{\label{exampleii}A walk with constrained angles: % \texttt{\detokenize\expandafter{\exampleii}}} % \end{center} % \end{figure} % % \begin{figure} % \begin{center} % \framebox{\exampleiii} % \caption{\label{exampleiii}A last example, with small relative % angles: \texttt{\detokenize\expandafter{\exampleiii}}} % \end{center} % \end{figure} % % \end{documentation} % % \begin{implementation} % % \section{\pkg{randomwalk} implementation} % % \subsection{Packages} % % The \pkg{expl3} bundle is loaded first. % %<*package> % \begin{macrocode} %<@@=randomwalk> % \end{macrocode} % % \begin{macrocode} \RequirePackage{expl3}[2017/11/14] \ProvidesExplPackage {randomwalk} {2018/12/28} {0.6} {Customizable random walks} \RequirePackage{xparse}[2017/11/14] % \end{macrocode} % % Load \pkg{pgfcore} for figures. % \begin{macrocode} \RequirePackage{pgfcore} % \end{macrocode} % % Load \pkg{lcg} for random numbers. % It needs to know the smallest and biggest random numbers that % should be produced, which we take to be $0$ and $\cs{c_@@_lcg_last_int} % = 2^{31}-2$. It will then store them in \cs{c@lcg@rand}: the |\c@| is % there because of how \LaTeXe{} defines counters. To make it clear that % |\c| has a very special meaning here, I do not follow \LaTeX3 naming % conventions. Also of note is that I use \cs{cr@nd} in \cs{@@_walk:}. % % It seems that the \pkg{lcg} package has to be loaded after the % document class, hence we do it \cs{AtBeginDocument}. Also worth noting % is the call to \cs{rand}, which avoids some very odd bug. % \begin{macrocode} \int_const:Nn \c_@@_lcg_last_int { \c_max_int - 1 } \AtBeginDocument { \RequirePackage [ first= 0 , last = \c_@@_lcg_last_int , counter = lcg@rand ] { lcg } \rand } % \end{macrocode} % % \subsection{Variables} % % \begin{variable}{\l_@@_internal_tl, \l_@@_internal_int} % Used for scratch assignments. % \begin{macrocode} \tl_new:N \l_@@_internal_tl \int_new:N \l_@@_internal_int % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_step_number_int} % The number of steps requested by the caller. % \begin{macrocode} \int_new:N \l_@@_step_number_int % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_relative_angles_bool, \l_@@_degrees_bool} % Booleans for whether angles are relative (keyval option), % and whether they are in degrees. % \begin{macrocode} \bool_new:N \l_@@_relative_angles_bool \bool_new:N \l_@@_degrees_bool % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_revert_random_bool} % Booleans for whether to revert the random seed to its original value % or keep the last value reached at the end of a random path. % \begin{macrocode} \bool_new:N \l_@@_revert_random_bool % \end{macrocode} % \end{variable} % % \begin{macro}{\@@_next_angle:, \@@_next_length:} % Set the \cs{l_@@_angle_fp} and \cs{l_@@_length_fp} of the next step, % most often randomly. % \begin{macrocode} \cs_new_protected:Npn \@@_next_angle: { } \cs_new_protected:Npn \@@_next_length: { } % \end{macrocode} % \end{macro} % % \begin{variable}{\l_@@_angle_fp, \l_@@_length_fp} % Angle and length of the next step. % \begin{macrocode} \fp_new:N \l_@@_angle_fp \fp_new:N \l_@@_length_fp % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_x_dim, \l_@@_y_dim} % Current coordinates: each \cs{pgfpathlineto} statement % goes from the previous value of these to the next. See % \cs{@@_walk_step:}. % \begin{macrocode} \dim_new:N \l_@@_x_dim \dim_new:N \l_@@_y_dim % \end{macrocode} % \end{variable} % % \begin{variable}{\l_@@_angles_seq, \l_@@_lengths_seq} % Sequences containing all allowed angles and lengths, as % floating point numbers. % \begin{macrocode} \seq_new:N \l_@@_angles_seq \seq_new:N \l_@@_lengths_seq % \end{macrocode} % \end{variable} % % \subsection{User command and key-value list} % % \begin{macro}{\RandomWalk, \randomwalk:n} % The user command \cs{RandomWalk} is based on the code-level % command \cs{randomwalk:n}, which simply does the setup and calls % the internal macro \cs{@@_walk:}. % \begin{macrocode} \DeclareDocumentCommand \RandomWalk { m } { \randomwalk:n {#1} } \cs_new_protected:Npn \randomwalk:n #1 { \@@_setup_defaults: \keys_set:nn { randomwalk } {#1} \@@_walk: } % \end{macrocode} % \end{macro} % % We introduce the keys for the package. % \begin{macrocode} \keys_define:nn { randomwalk } { number .value_required:n = true , length .value_required:n = true , angles .value_required:n = true , number .int_set:N = \l_@@_step_number_int , length .code:n = { \@@_setup_length:n {#1} } , angles .code:n = { \@@_setup_angles:n {#1} } , degree .bool_set:N = \l_@@_degrees_bool , degrees .bool_set:N = \l_@@_degrees_bool , angles-relative .bool_set:N = \l_@@_relative_angles_bool , revert-random .bool_set:N = \l_@@_revert_random_bool , } % \end{macrocode} % % \subsection{Setup} % % \begin{macro}{\@@_setup_defaults:} % The package treats the length of steps, and the angle, % completely independently. The function \cs{@@_next_length:} % contains the action that decides the length of the next step, while % the function \cs{@@_next_angle:} pertains to the angle. % % \cs{@@_setup_defaults:} sets the default values before processing the % user's key-value input. This also sets initial values of variables % that currently cannot be altered through keys, because it might be % good to provide keys for their initial values too later on. % \begin{macrocode} \cs_new_protected:Npn \@@_setup_defaults: { \int_set:Nn \l_@@_step_number_int {10} \cs_gset_protected:Npn \@@_next_angle: { \@@_fp_set_rand:Nnn \l_@@_angle_fp { 0 } { 360 } } \cs_gset_protected:Npn \@@_next_length: { \fp_set:Nn \l_@@_length_fp {10} } \bool_set_false:N \l_@@_revert_random_bool \bool_set_false:N \l_@@_relative_angles_bool \fp_zero:N \l_@@_angle_fp \fp_zero:N \l_@@_length_fp \dim_zero:N \l_@@_x_dim \dim_zero:N \l_@@_y_dim } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_setup_length:n} % Convert each item in the comma list into a floating point, then % define \cs{@@_next_length:} to set \cs{l_@@_length_fp} to a random % floating point in the list. % \begin{macrocode} \cs_new_protected:Npn \@@_setup_length:n #1 { \seq_set_split:Nnn \l_@@_lengths_seq { , } {#1} \seq_set_map:NNn \l_@@_lengths_seq \l_@@_lengths_seq { \dim_to_fp:n {##1} } \cs_gset_protected:Npn \@@_next_length: { \@@_get_rand_seq_item:NN \l_@@_lengths_seq \l_@@_internal_tl \fp_set:Nn \l_@@_length_fp { \l_@@_internal_tl } } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_setup_angles:n} % Two complications compared to \cs{@@_setup_length:n}. First, the % angle can be given in radians rather than degrees: then add |rad| % after the randomly chosen value (in principle it would be better to % convert angles once and for all at the beginning, but that % interacts in a complicated way with the fact that keys can be given % in any order). Second, angles can be relative, in which case we % use \cs{fp_add:Nn} to take the last angle into account. % \begin{macrocode} \cs_new_protected:Npn \@@_setup_angles:n #1 { \seq_set_split:Nnn \l_@@_angles_seq { , } {#1} \seq_set_map:NNn \l_@@_angles_seq \l_@@_angles_seq { \fp_to_tl:n {##1} } \cs_gset_protected:Npn \@@_next_angle: { \@@_get_rand_seq_item:NN \l_@@_angles_seq \l_@@_internal_tl \bool_if:NF \l_@@_degrees_bool { \tl_put_right:Nn \l_@@_internal_tl { rad } } \bool_if:NTF \l_@@_relative_angles_bool { \fp_add:Nn } { \fp_set:Nn } \l_@@_angle_fp { \l_@@_internal_tl } } } % \end{macrocode} % \end{macro} % % \subsection{Drawing} % % \begin{macro}{\@@_walk:} % We are ready to define \cs{@@_walk:}, which draws a \pkg{pgf} % picture of a random walk with the parameters set up by the % \texttt{keys}. We reset coordinates to zero originally. % Then draw the relevant \pkg{pgf} picture by repeatedly calling % \cs{@@_walk_step:}. % \begin{macrocode} \cs_new_protected:Npn \@@_walk: { \@@_walk_start: \prg_replicate:nn { \l_@@_step_number_int } { \@@_walk_step: } \bool_if:NF \l_@@_revert_random_bool { \int_gset_eq:NN \cr@nd \cr@nd } \@@_walk_stop: } % \end{macrocode} % \cs{cr@nd} is internal to the lcg package. % \end{macro} % % \begin{macro}{\@@_walk_start:, \@@_walk_line:, \@@_walk_stop:} % These functions encapsulate all of the \pkg{pgf}-related code. The % \texttt{start} function begins the pgfpicture environment and % starts a path at position (x,y). The \texttt{line} function adds % to the path a line from the previous position to the new (x,y). % The \texttt{stop} function draws the path constructed by % \cs{@@_walk_step:} and ends the pgfpicture environment. % \begin{macrocode} \cs_new_protected:Npn \@@_walk_start: { \begin{pgfpicture} \pgfpathmoveto { \pgfpoint { \l_@@_x_dim } { \l_@@_y_dim } } } \cs_new_protected:Npn \@@_walk_line: { \pgfpathlineto { \pgfpoint { \l_@@_x_dim } { \l_@@_y_dim } } } \cs_new_protected:Npn \@@_walk_stop: { \pgfusepath { stroke } \end{pgfpicture} } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_walk_step:} % \cs{@@_walk_step:} calls \cs{@@_next_length:} and % \cs{@@_next_angle:} to determine the length and angle of the new % step. This is then converted to cartesian coordinates and added to % the previous end-point. Finally, we call \pkg{pgf}'s \cs{pgfpathlineto} to % produce a line to the new point. % \begin{macrocode} \cs_new_protected:Npn \@@_walk_step: { \@@_next_length: \@@_next_angle: \dim_add:Nn \l_@@_x_dim { \fp_to_dim:n { \l_@@_length_fp * cosd ( \l_@@_angle_fp ) } } \dim_add:Nn \l_@@_y_dim { \fp_to_dim:n { \l_@@_length_fp * sind ( \l_@@_angle_fp ) } } \@@_walk_line: } % \end{macrocode} % \end{macro} % % \subsection{On random numbers and items} % % For random numbers, the interface of \pkg{lcg} is not quite enough, so % we provide our own \LaTeX3-y functions. Also, this will allow us to % change quite easily our source of random numbers. % % \begin{macro}{\@@_fp_set_rand:Nnn} % We also need floating point random numbers, assigned % to the variable |#1|. % \begin{macrocode} \cs_new_protected:Npn \@@_fp_set_rand:Nnn #1#2#3 { \rand \fp_set:Nn #1 { #2 + (#3 - (#2)) * \c@lcg@rand / \c_@@_lcg_last_int } } % \end{macrocode} % \end{macro} % % \begin{macro}{\@@_get_rand_seq_item:NN} % We can now pick an element at random from a sequence. If the % sequence has a single element, no need for randomness. % \begin{macrocode} \cs_new_protected:Npn \@@_get_rand_seq_item:NN #1#2 { \int_set:Nn \l_@@_internal_int { \seq_count:N #1 } \int_compare:nTF { \l_@@_internal_int = 1 } { \tl_set:Nx #2 { \seq_item:Nn #1 { 1 } } } { \rand \tl_set:Nx #2 { \seq_item:Nn #1 { 1 + \int_mod:nn { \c@lcg@rand } { \l_@@_internal_int } } } } } % \end{macrocode} % \end{macro} % % \begin{macrocode} % % \end{macrocode} % % \end{implementation} % % \endinput