% \iffalse % ------------------------------------------------------------------- % % Copyright 2002--2004, Daniel H. Luecking % % Splines.mp 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 % % and version 1.3 or later is part of all distributions of LaTeX version % 2003/12/01 or later. % % Splines has maintenance status "author-maintained". The Current Maintainer % is Daniel H. Luecking. The Base Interpreter is MetaPost (or Metafont). % %<*driver> \ProvidesFile{splines.dtx} [2005/02/05 v0.2 Metafont/post macros to compute splines.]% \documentclass[draft]{ltxdoc} \usepackage{docmfp} \addtolength{\textwidth}{.5878pt} \def\mytt{\upshape\mdseries\ttfamily} \renewcommand\marg[1]{{\mytt \{#1\}}} \renewcommand\oarg[1]{{\mytt [#1]}} \renewcommand\parg[1]{{\mytt (#1)}} \renewcommand{\meta}[1]{{$\langle$\rmfamily\itshape#1$\rangle$}} \DeclareRobustCommand\cs[1]{{\mytt\char`\\#1}} \def\prog#1{{\mdseries\scshape #1}} \def\MF{\prog{meta\-font}} \def\MP{\prog{meta\-post}} \def\CMF{\prog{Meta\-font}} \def\CMP{\prog{Meta\-post}} \def\opt#1{{\sffamily\upshape#1}} \def\mfc#1{{\mytt#1}} \let\env\mfc \let\file\mfc \let\gbc\mfc \renewcommand\{{{\mytt\char`\{}} \renewcommand\}{{\mytt\char`\}}} \renewcommand\|{${}\mathrel{|}{}$} \makeatletter \newcommand\bsl{{\mytt\@backslashchar}} % Stupid lists! \def\@listi{\leftmargin\leftmargini \parsep \z@ \@plus\p@ \@minus\z@ \topsep 4\p@ \@plus\p@ \@minus2\p@ \itemsep\parsep} \let\@listI\@listi \@listi \renewcommand\labelitemi{\normalfont\bfseries \textendash} \renewcommand\labelitemii{\textasteriskcentered} \renewcommand\labelitemiii{\textperiodcentered} \leftmargini\parindent % Stupid index! \def\usage#1{\textrm{#1}} \def\index@prologue{\section*{Index}\markboth{Index}{Index}Numbers refer to the page where the corresponding entry is described.} \def\IndexParms{% \parindent \z@ \columnsep 15pt \parskip 0pt plus 1pt \rightskip 5pt plus2em \mathsurround \z@ \parfillskip=-5pt \small % less hanging: \def\@idxitem{\par\hangindent 20pt}% \def\subitem{\@idxitem\hspace*{15pt}}% \def\subsubitem{\@idxitem\hspace*{25pt}}% \def\indexspace{\par\vspace{10pt plus 2pt minus 3pt}}} \renewcommand\routinestring{} \renewcommand\variablestring{\space(var.)} % Why does every command have to be indexed twice? \renewcommand\SpecialMfpIndex[3]{\@bsphack \index{% \string#1\actualchar \string\verb\quotechar*\verbatimchar\string#1\verbatimchar #2 \encapchar usage}% \@esphack} \def\close@crossref{\SpecialEscapechar{:}} \makeatother \def\VariableIndex#1{\SpecialMfpIndex{#1}{\variablestring}{}} \def\RoutineIndex #1{\SpecialMfpIndex{#1}{}{}} \title{Macros to Compute Splines\thanks{This file has version number \fileversion, last revised \filedate.}} \author{Dan Luecking} \date{\filedate} \SpecialEscapechar{:} \def\bslash{:} \DisableCrossrefs \CodelineIndex \AlsoImplementation \begin{document} \DeleteShortVerb{\|} \DocInput{splines.dtx} \end{document} % %\fi % % \CheckSum{25} % \CharacterTable % {Upper-case \A\B\C\D\E\F\G\H\I\J\K\L\M\N\O\P\Q\R\S\T\U\V\W\X\Y\Z % Lower-case \a\b\c\d\e\f\g\h\i\j\k\l\m\n\o\p\q\r\s\t\u\v\w\x\y\z % Digits \0\1\2\3\4\5\6\7\8\9 % Exclamation \! Double quote \" Hash (number) \# % Dollar \$ Percent \% Ampersand \& % Acute accent \' Left paren \( Right paren \) % Asterisk \* Plus \+ Comma \, % Minus \- Point \. Solidus \/ % Colon \: Semicolon \; Less than \< % Equals \= Greater than \> Question mark \? % Commercial at \@ Left bracket \[ Backslash \\ % Right bracket \] Circumflex \^ Underscore \_ % Grave accent \` Left brace \{ Vertical bar \| % Right brace \} Tilde \~} % % \catcode`\_=12 % \GetFileInfo{splines.dtx} % \maketitle % % \StopEventually{\PrintIndex} % A cubic spline through a set of points is a curve obtained by joining % each point to the next with a cubic parametrized curve, where adjoining % cubics must have matching first and second derivative at their common % point. % % It is possible for \MP{} to compute the necessary controls. % Unfortunately, the controls are not uniquely determined unless the curve % is required to be closed. For open curves, there is need for two % additional equations at the end points. A `relaxed spline' is produced % if we require that the second derivative is $(0,0)$ at those points. % For a closed curve, the equality of the first and second derivatives at % the common beginning/ending point gives the needed additional equations. % % Note that this equates \emph{time} derivatives, so this works best when % points are relatively evenly spaced and so the speed is relatively % uniform. If points are differently spaced then the relatively slower % speed between closely spaced points allows sharper turns without large % second derivatives. Curves produced tend to have a more natural look, % and relaxed splines are most suitable for smoothing data that is % obtained by taking observations at evenly spaced times. Still, the % technique is somewhat unstable when points are closely spaced, for % example when a small change in the position of one point can produce a % large change in its direction when viewed from another point. % % Start with version control information. % \begin{macrocode} %<*package> if known splines_fileversion: endinput fi; string splines_fileversion; splines_fileversion := "2006/09/25, v0.2a"; message "Loading splines.mp " & splines_fileversion; % \end{macrocode} % % \DescribeRoutine{list_to_array} % Now for a command that takes a variable name \gbc{(suffix arr)} and % copies a list of pairs to \mfc{arr1}, \mfc{arr2}, etc.. % The suffix must be declared by the calling program so that \mfc{arr} % is numeric but \mfc{arr[\,]} are pairs, for example by ``\mfc{save arr; pair % arr[\,];}''. % \begin{macrocode} def list_to_array (suffix arr) (text list) = arr := 0; for _itm = list : arr[incr arr] := _itm; endfor enddef; % \end{macrocode} % % \DescribeRoutine{compute_spline} % In this command we generate the equations common to all cubic % splines: the equality of derivatives at all interior points. % This command accepts three suffixes: \gbc{points}, \gbc{pr}, and % \gbc{po} which should represent previously declared arrays of pairs. % \gbc{points} is the array of points to be connected, and must be known. % Arrays \gbc{pr} and \gbc{po} \emph{must} be unknown and will hold the % computed control points. See the code of \mfc{mkrelaxedspline} for % an example of how this was arranged for the variables \mfc{rs_pr} and % \mfc{rs_po}. % % Contrary to the previous (unreleased) version, \mfc{compute_spline} % takes a boolean argument and appends the same equations at the first (= % last) point. % % \DescribeRoutine{mkrelaxedspline} % The first of these macros appends the necessary additional equations to % get zero second derivatives at the ends. % \DescribeRoutine{mkclosedspline} % The second simply calls \mfc{compute_spline} with the boolean set to % true. Both return the computed path. In theory the knowledgeable user % can call \gbc{compute_spline (false)}, append a choice of equations for % the ends, and then call \gbc{mksplinepath (false)}. % % \DescribeRoutine{dospline} % This version accepts a list of pairs and produces a spline through % them. It simply stores the list in an array and calls the appropriate % version that operates on an array. % \begin{macrocode} def compute_spline (expr closed) (suffix points, pr, po) = % interior equations: for j= 2 upto points - 1 : % equate first derivatives: po[j] + pr[j] = 2 points[j]; % and second derivatives: pr[j+1] + 2 pr[j] = 2 po[j] + po[j-1]; endfor % for a closed curve, the first and last are also interior: if closed: po 1 + pr 1 = 2 points 1; po[points] + pr[points] = 2 points[points]; pr 2 + 2 pr 1 = 2 po 1 + po[points]; pr 1 + 2 pr[points] = 2 po[points] + po[points-1]; fi enddef; vardef mksplinepath (expr closed) (suffix points, pr, po) = points1..controls po1 and for j = 2 upto points if not closed: -1 fi: pr[j]..points[j]..controls po[j] and endfor if closed: pr 1..cycle else: pr[points]..points[points] fi enddef; vardef mkrelaxedspline (suffix pnts) = save rs_pr, rs_po; pair rs_po[], rs_pr[]; % Equate second derivative to zero at both end points rs_pr 2 + pnts 1 = 2 rs_po 1 ; pnts[pnts] + rs_po[pnts-1] = 2 rs_pr[pnts]; compute_spline (false) (pnts, rs_pr, rs_po); mksplinepath (false) (pnts, rs_pr, rs_po) enddef; vardef mkclosedspline (suffix pnts) = save cs_pr, cs_po; pair cs_pr[], cs_po[]; compute_spline (true) (pnts, cs_pr, cs_po); mksplinepath (true) (pnts, cs_pr, cs_po) enddef; vardef dospline (expr closed) (text the_list) = save _sp; pair _sp[]; list_to_array (_sp) (the_list); if closed : mkclosedspline (_sp) else: mkrelaxedspline (_sp) fi enddef; % \end{macrocode} % The above computations produce a $2$-dimensional spline. A $1$-dimensional % cubic spline would be a function $f(t)$ with numeric values rather % than pair values. Such are often used to interpolate functions. That is, % given pairs $(x\sb j,y\sb{j})$, and assuming they lie on the graph of % some function (generally unknown), fill in the graph with $y = f(x)$ % where $f$ is a cubic function of $x$ in each interval $x\sb j < x < x\sb % {j+1}$, making sure that the resulting graph is as smooth as possible at % the points $x\sb j$. % % The requirements on our $2$-dimensional path are the following: % \begin{enumerate} % \item The $j$th link should connect $(x\sb{j},y\sb{j})$ to $(x\sb{j+1}, % y\sb{j+1})$. % \item The $x$-part of that link should increase linearly from $x\sb{j}$ to % $x\sb{j+1}$ as $t$ goes from $0$ to $1$. % \item The $y$-part should be a cubic $y = f(x)$. % \item The $x$-derivatives $df/dx$ and $d^2f/dx^2$ should match at the % connecting points. % \end{enumerate} % % Two necessary equations for converting between $x$ and $t$ coordinates % are: % \begin{equation}\label{first} % x = x\sb{j} + t \Delta x\sb{j} % \end{equation} % (where $\Delta x\sb{j} = x\sb{j+1} - x\sb{j}$) and % \begin{equation}\label{second} % \frac{df}{dt} = \frac{dx}{dt}\frac{df}{dx} = (x\sb{j+1} - % x\sb{j}) \frac{df}{dx}. % \end{equation} % Thus we want to choose controls so that (\ref{first}) is maintained and % so that $x$-derivatives match. It turns out that this requires controls % at % \begin{equation} % \begin{array}{c} % (x\sb{j}, y\sb{j}) - (\Delta x\sb{j-1}, s\sb{j} \Delta x\sb{j-1})/3\\ % (x\sb{j}, y\sb{j}) + (\Delta x\sb{j} , s\sb{j} \Delta x\sb{j} )/3 % \end{array} % \end{equation} % where $s\sb{j}$ is the slope (derivative) at $x\sb{j}$. These control % points will produce matching slopes regardless of the values % chosen for the $s\sb j$. To get matching second derivatives we need the % same conditions as in parametric splines. But those equations simplify % to the form: % \begin{displaymath} % s\sb{j+1} dx\sb{j} - 2s\sb{j} (dx\sb{j} + dx\sb{j-1}) + % s\sb{j-1}dx\sb{j-1} % = 3y\sb{j+1} - 3y\sb{j-1}. % \end{displaymath} % As with 2-D splines there can be almost any equations at the end points. % For a relaxed spline we equate the second derivatives to 0. To get a % periodic function, we equate the slope and second derivative at % beginning to those at the end. This makes it possible to put a shifted % copy of the graph with starting point at the end of the original and % have the same smoothness at that connection as at the other points. % % \DescribeRoutine{compute_fcnspline} % This issues the equation for the slopes (array \gbc{sl} of % \emph{unknown} numerics). The array \gbc{points} contains the $(x,y)$ % values and \gbc{dx} is a temporary numeric array which will be % overwritten if known. % % \DescribeRoutine{mkfcnsplinepath} % This simply assembles the path from the information computed by the % above equations (and the extra equations given in the calling command). % % \DescribeRoutine{mkrelaxedfcnspline} % This sets up arrays for the \gbc{dx} and \gbc{sl} parameters of % \mfc{compute_fcnspline}, emit the necessary endpoint equations (zero % second derivatives) and calls the previous two routines. % % \DescribeRoutine{mkperiodicfcnspline} % This does the same as the previous command, but the endpoint equations % make the first and second derivatives at the start equal to those at % the end. % % \DescribeRoutine{fcnspline} % Finally, this command copies a list of pairs into an array and calls the % appropriate command to process them. % \begin{macrocode} def compute_fcnspline (suffix points, dx, sl) = % Get delta_x: for j = 1 upto points - 1: dx[j] := xpart (points[j+1]-points[j]); endfor for j=2 upto points - 1: sl[j + 1] * dx[j] + 2sl[j]*(dx[j] + dx[j-1]) + sl[j-1]*dx[j-1] = 3*ypart(points[j+1] - points[j-1]); endfor enddef; vardef mkfcnsplinepath (suffix points, dx, sl) = points1..controls (points1 + (1, sl1)*dx1/3) and for j = 2 upto points - 1: (points[j] - (1, sl[j])*dx[j-1]/3) ..points[j].. controls (points[j] + (1,sl[j])*dx[j]/3) and endfor (points[points] - (1,sl[points])*dx[points-1]/3)..points[points] enddef; vardef mkperiodicfcnspline (suffix pnts) = save _sl, _dx; numeric _dx[], _sl[]; compute_fcnspline (pnts, _dx, _sl); % periodicity equations: _sl 1 = _sl[pnts]; _sl 2 * _dx 1 + 2 _sl 1 * _dx 1 + 2 _sl[pnts] * _dx[pnts-1] + _sl[pnts-1] * _dx[pnts-1] = 3 * ypart(pnts[2] - pnts[pnts-1]); mkfcnsplinepath (pnts, _dx, _sl) enddef; vardef mkrelaxedfcnspline (suffix pnts) = save _sl, _dx; numeric _dx[], _sl[]; compute_fcnspline (pnts, _dx, _sl); % relaxation equations. _sl 2 * _dx 1 + 2 _sl1 * _dx 1 = 3 * ypart(pnts2 - pnts1); _sl[pnts-1] * _dx[pnts-1] + 2 _sl[pnts] * _dx[pnts-1] = 3 * ypart(pnts[pnts] - pnts[pnts-1]); mkfcnsplinepath (pnts, _dx, _sl) enddef; vardef fcnspline (expr periodic) (text the_list) = save _fs; pair _fs[]; list_to_array (_fs) (the_list); if periodic: mkperiodicfcnspline (_fs) else: mkrelaxedfcnspline (_fs) fi enddef; % % \end{macrocode} % \clearpage %\Finale