%% \CheckSum{145} % \iffalse % File: paramcalc.dtx % Copyright (C) 2026 Miguel V. S. Frasson (mvsfrasson@gmail.com) % % This package may be distributed under the terms of the LaTeX % Project Public License, as described in lppl.txt in the base % LaTeX distribution, either version 1.3 or (at your option) % any later version. % %<*driver> \documentclass{ltxdoc} \usepackage[T1]{fontenc} \usepackage[lining]{ebgaramond} \usepackage{ebgaramond-maths,textcomp} \usepackage[cmintegrals,cmbraces]{newtxmath} \let\epsilon\varepsilon \usepackage{xcolor,microtype,amsmath,amstext,tikz,centerlastline} \usepackage{paramcalc} % \usepackage[margin=1in]{geometry} \newcommand{\var}[1]{\texttt{\expandafter\string\csname #1\endcsname}} \RecordChanges \begin{document} \DocInput{paramcalc.dtx} \end{document} % % \fi % % %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% % % \StopEventually % % \changes{1.0}{2026/01/27}{Initial version} % % \title{\textsc{paramcalc}: compute parameters to fit condition} % \author{Miguel V.\ S.\ Frasson\\(\texttt{mvsfrasson@gmail.com})} % \date{2026, jan. 27} % % \maketitle % % \begin{abstract}\centerlastline\noindent % Sometimes one wants to get font size or letter spacing so that % text fits a desired length, or some unit length so that a picture % fits some desired size, for instance. This package implements % macros to compute such parameters to fit a condition. % \end{abstract} % % \section{The purpose of this package} % Suppose that one wants to create a nice logo with two lines of same % size, first line “AWESOME” and second line “example of use for % \texttt{paramcalc}”. We look for font size of first line, so that % the lines have the same width. This is the desired result: % % \newlength{\lineIwd} % \newlength{\lineIIwd} % \newcommand{\mytext}{\Large example of use for paramcalc} % \settowidth{\lineIIwd}{\mytext} % % \paramcalc{12}{50}{\lineIIwd}[\lineIwd]{ % \settowidth{\lineIwd}{\fontsize{\x}{\x}\selectfont \textls*{AWESOME}} % } % % \begin{center} % {\fontsize{\x}{\x}\selectfont \textls*{AWESOME}\par} % \mytext \vspace*{\bigskipamount} % \end{center} % % Instead of setting font size by hand\footnote{As width is % proportional to font size, one could also compute font size by % proportion, measuring texts of both lines.} and visually trying to get the % desired condition, we want to compute such font size for line 1. % % This can be acomplished with \texttt{paramcalc} with the following % code (make sure you use scalable fonts\footnote{Computer Modern are % not scalable, but Latin Modern (package \texttt{lmodern}), EB % Garamond (package \texttt{ebgaramond}) and many others are.}): % % \enlargethispage{\baselineskip} % \begin{verbatim} %\newcommand{\mytext}{\Large example of use for paramcalc} %\newlength{\lineIIwd} %\settowidth{\lineIIwd}{\mytext} % %\newlength{\lineIwd} %\paramcalc{12}{50}{\lineIIwd}[\lineIwd]{ % % compute \lineIwd as function of fontsize \x % \settowidth{\lineIwd}{\fontsize{\x}{\x}\selectfont \textls*{AWESOME}} %} % %Showing the result. %\begin{center} % {\fontsize{\x}{\x}\selectfont \textls*{AWESOME}\par} % \mytext %\end{center} %\end{verbatim} % % It you use \texttt{\string\x} in the document, you get the computed % value \x. % % \section{Usage} % % To compute a desired parameter, the situation can be reduced to find % a zero of a real function. In the example above, we wanted that the % length $y(x)$ of first line as function of its font size $x$ should % match the length $y_0$ of second line, so that we need to find the % zero of the function $f(x) = y(x) - y_0$. % % The computation is done by \texttt{\string\paramcalc} macro.\medskip % % \DescribeMacro{\paramcalc}^^A % \newcommand{\varp}[1]{\textlangle\makebox{\textit{#1}}\textrangle}^^A % \newcommand{\varm}[1]{\texttt{\{}\varp{#1}\texttt{\}}}^^A % \newcommand{\varopt}[1]{\texttt{[}\varp{#1}\texttt{]}} % \noindent\texttt{\string \paramcalc}\varopt{x macro}^^A % \varm{x initial}\varm{x final}\varopt{$\epsilon$}^^A % \varm{y desired}\varopt{y macro}\varm{body}\medskip % % The optional \varp{x macro} defaults to \texttt{\string\x}, the % optional \varp{y macro} defaults to \texttt{\string\y} and both % should be control sequences. \varp{y macro} and \varp{y desited} % could be lengths, converted to numbers as pt. % % \varp{body} should be code that computes \varp{y macro} for an % arbitrary value of \varp{x macro}, supposed to expand to a decimal % value. The result is stored in \varp{x macro} after its % computation. If its value is in between \varp{x initial} and % \varp{x final}, convergence is ensured. Often\footnote{I could not % figure out any real life example where the function was not affine.} % the functions involved are affine (for instance, a width is % proportional a font size), and in this case convergence is achieved % after just one step. % % The convergence is achieved when % $\vert \text{\varp{y macro}} - \text{\varp{y desired}}\vert < % \epsilon$. % The value of $\epsilon$, where $\epsilon$ is given in optional arg % \varp{epsilon} and defaults to 0.05. If it doesn't happen after a % maximum number of steps, an error is signaled. Then, probably de % desired $x$ is outside initial interval. % % There are situations where only integer values of $x$ are suitable, % like in the optional parameter \textit{n} for % \texttt{\string\textls}\varopt{n}\varm{text}. In such cases, use % \texttt{\string\intparamcalc}, with same arguments. The only % difference is that \varp{x macro} only assume integer values, but % \varp{y macro} still are supposed to assume real values.\medskip % % \DescribeMacro{\intparamcalc}^^A % \noindent\texttt{\string \intparamcalc}\varopt{x macro}^^A % \varm{x initial}\varm{x final}\varopt{$\epsilon$}^^A % \varm{y desired}\varopt{y macro}\varm{body}\medskip % % \textit{Attention}: The numerical method implemented in this package % is not suitable when $y$ assumes (too) discrete values or isn't a % continuous function of $x$ (it its where real-valued) like when $y$ % counts lines, for instance. % % \subsection{Package options} % % \begin{itemize} % \item \texttt{maxsteps} = \varp{n} : sets maximum number of steps % before giving up. Defaults to 20. % \item \texttt{epsilon} = \varp{epsilon} : sets the convegence % criterium $\epsilon$ to \varp{epsilon}. Defaults to 0.05. % \end{itemize} % % \section{Examples} % % Creating a logo for the book “História Geral do Brasil”, in three % lines. % \begin{itemize} % \item The first two lines in font size 20pt of size; % \item For the first line we compute letter spacing to fit size of 2nd line; % \item For the third line, we compute font size, keeping same size. % \end{itemize} % %\begin{center} % \fontsize{20}{22}\bfseries % % \newlength{\lIIwd} \newlength{\auxwd} % \settowidth{\lIIwd}{\textls*{GERAL \,DO}} % % \intparamcalc[\lIls]{0}{200}{\lIIwd}[\auxwd]{ % \settowidth{\auxwd}{\textls*[\lIls]{HISTÓRIA}}} % % \paramcalc[\lIIIfs]{20}{50}{\lIIwd}[\auxwd]{ % \settowidth{\auxwd}{\fontsize{\lIIIfs}{\lIIIfs}\selectfont \textls*{BRASIL}}} % % \textls*[\lIls]{HISTÓRIA} \\ \textls*{GERAL \,DO} \\ % \fontsize{\lIIIfs}{\lIIIfs}\selectfont \textls*{BRASIL} % \end{center} % % This is achieved with the code: % \begin{verbatim} %\begin{center} % \fontsize{20}{22}\bfseries % % % base length \lIIwd: line 2 with \textls* and \bfseries % \newlength{\lIIwd} \newlength{\auxwd} % \settowidth{\lIIwd}{\textls*{GERAL \,DO}} % % % compute letter spacing \lIls for 1st line: body compute \auxwd % \intparamcalc[\lIls]{0}{200}{\lIIwd}[\auxwd]{ % \settowidth{\auxwd}{\textls*[\lIls]{HISTÓRIA}}} % % % compute font size \lIIIfs for 3rd line: body compute \auxwd % \paramcalc[\lIIIfs]{20}{50}{\lIIwd}[\auxwd]{ % \settowidth{\auxwd}{\fontsize{\lIIIfs}{\lIIIfs}\selectfont \textls*{BRASIL}}} % % % typesetting the logo % \textls*[\lIls]{HISTÓRIA} \\ \textls*{GERAL \,DO} \\ % \fontsize{\lIIIfs}{\lIIIfs}\selectfont \textls*{BRASIL} %\end{center} % \end{verbatim} % % \section{Numerical method implemented} % % To ensure convergense (with a root in $[a,b]$), we implement Illinois % algorithm\footnote{Dowell, M., Jarratt, P. A modified regula falsi % method for computing the root of an equation. \textit{BIT Numerical % mathematics} \textbf{11}, 168–174 % (1971). \url{https://doi.org/10.1007/BF01934364}}, an improvement % over False Position method (\textit{regula falsi}): % % Assuming that $f:I\to \mathbb{R}$ is a continuous function, % end-points $a, b \in I$, $f(a)\cdot f(b) < 0$, so $f$ has a root in $[a,b]$. % % Let $c$ be the root of the secant line that connects $(a,f(a))$ and % $(b,f(b))$. % % \begin{center} % \begin{tikzpicture} % \draw[->] (-0.5,0) -- (2.5,0); \draw[->] (0,-1.5) -- (0,1.5); % \draw[thick] (0.5,-1) coordinate (a) % (a|-0,0) node[above] {$a$} % (1.8,1.5) coordinate (b) % (b|-0,0) node[below right] {$b$} % (a) -- (b) % (1.02,0) coordinate (x) node[below right] {$c$}; % \draw[dashed] (a|-0,0) -- (a) -- (a-|b) -- (b); % \fill (a) circle[radius=1pt] (b) % circle[radius=1pt] (x) circle[radius=1pt]; % \end{tikzpicture} % \end{center} % so, by similarity of triangles, % \begin{equation}\label{eq:c} % \Delta = \frac{b-a}{f(b)-f(a)} = \frac{b-c}{f(b)} \implies % c = b - f(b)\Delta. % \end{equation} % % The Illinois algorithm for False Position puts the following iteraction: % \begin{align} % (a,f(a)) & = % \begin{cases} % \bigl(c,f(c)\bigr), & \text{if } f(b)f(c)< 0,\\ % \bigl(a,\frac{f(a)}{2}\bigr), & \text{otherwise} % \end{cases}\label{eq:iter1} \\ % (b,f(b)) & = (c,f(c)) \label{eq:iter2} % \end{align} % % \section{Code} % % Identidication of the package. % \begin{macrocode} \NeedsTeXFormat{LaTeX2e} \ProvidesPackage{paramcalc}[2026/01/27 v1.0 compute params to fit conditions] % \end{macrocode} % % We use \LaTeX3 codebase. % \begin{macrocode} \ExplSyntaxOn % \end{macrocode} % % There are two versions of the solving macros: real roots are % admitted or only integer roots (like parameters for % \texttt{\string\textls[\string\x]\{\textit{text}\}}). We do a % single implementation and the cases are regulated by a boolen. % \begin{macrocode} \bool_new:N \g_PARC_int_bool % \end{macrocode} % % The iteraction will end when $|f(c)|<\epsilon$. % % Floating point variables for $a$, $b$, $f(a)$, $f(b)$, $c$, $f(c)$ % and $\Delta$: % \begin{macrocode} \fp_new:N \l_PARC_a_fp \fp_new:N \l_PARC_b_fp \fp_new:N \l_PARC_fa_fp \fp_new:N \l_PARC_fb_fp \fp_new:N \l_PARC_x_fp \fp_new:N \l_PARC_fx_fp \fp_new:N \l_PARC_delta_fp % \end{macrocode} % Also, a counter for steps taken, in case of divergence. % \begin{macrocode} \int_new:N \l_PARC_steps_int % \end{macrocode} % % Defining package option for maximum number of steps. % \begin{macrocode} \int_new:N \l_PARC_maxsteps_int \int_set:Nn \l_PARC_maxsteps_int { 20 } \keys_define:nn {paramcalc} { maxsteps .int_set:N = \l_PARC_maxsteps_int } % \end{macrocode} % % Defining package option for default epsilon (stop criterium). % \begin{macrocode} \fp_new:N \l_PARC_epsilon_fp \fp_set:Nn \l_PARC_epsilon_fp { 0.05 } \keys_define:nn {paramcalc} { epsilon .fp_set:N = \l_PARC_epsilon_fp } % \end{macrocode} % % Passing options to package. % \begin{macrocode} \DeclareOption*{ \keys_set:ne { paramcalc } { \CurrentOption } } \ProcessOptions % \end{macrocode} % % \DescribeMacro{\PARC\_paramcalc:NnnnnNn} % The arguments of $\mathtt{\string\PARC\_paramcalc:NnnnnNn}$ % are\\ % \texttt{\#1} = macro that holds value for $x$ (default to % \texttt{\string\x} later on)\\ % \texttt{\#2} = $a$\\ % \texttt{\#3} = $b$\\ % \texttt{\#4} = $\epsilon$\\ % \texttt{\#5} = $y_0$\\ % \texttt{\#6} = macro that stores computed $y(x)$ (default to % \texttt{\string\y} later on)\\ % \texttt{\#7} = body of code that computes $y(x)$ and stores value in % \texttt{\#6}. % \begin{macrocode} \cs_new:Nn \PARC_paramcalc:NnnnnNn { % \end{macrocode} % Store $a$ from \texttt{\#2} in \var{l_PARC_a_fp} and set % \texttt{\#1} as the decimal representation of $a$, taking care if % integer values must be used. % \begin{macrocode} \fp_set:Nn \l_PARC_a_fp { #2 } \bool_if:NTF \g_PARC_int_bool { \tl_set:Ne #1 { \fp_to_int:n { #2 } } } { \tl_set:Ne #1 { \fp_to_decimal:n { #2 } } } % \end{macrocode} % Call body to get $y(x)$ in \texttt{\#6} % \begin{macrocode} #7 % \end{macrocode} % Save $f(x) = y(x) - y_0$ in \var{l_PARC_fa_fp} % \begin{macrocode} \fp_set:Nn \l_PARC_fa_fp { #6 - (#5) } % \end{macrocode} % % Do as before, now for $b$ stored in \texttt{\#3}: % \begin{macrocode} \fp_set:Nn \l_PARC_b_fp { #3 } \bool_if:NTF \g_PARC_int_bool { \tl_set:Ne #1 { \fp_to_int:n { #3 } } } { \tl_set:Ne #1 { \fp_to_decimal:n { #3 } } } #7 \fp_set:Nn \l_PARC_fb_fp { #6 - (#5) } % \end{macrocode} % % In many situations, the function $f(x)$ is affine and the secant % method converges after the first iteration, so it really doesn't % matter if $f(a)f(b)<0$, so we trigger just a warning if signs of % $f(a)$ and $f(b)$ are the same. % \begin{macrocode} \fp_compare:nNnT { (\l_PARC_fa_fp) * (\l_PARC_fb_fp) } > { 0 } { \msg_warning:nn {paramcalc} { The ~values ~of ~\string#6 ~are ~{\fp_to_decimal:N \l_PARC_fa_fp} ~and ~{\fp_to_decimal:N \l_PARC_fa_fp}, ~both \fp_compare:nNnTF { \l_PARC_fa_fp } > 0 { ~above } { ~below } ~{\fp_to_decimal:n {#5}} ~so ~convergence ~is ~not ~ensured.\\ Try ~to ~set ~initial ~values ~that ~surround ~the ~root. } } % \end{macrocode} % \bigskip % % Now the loop. Last iteraction is supposed to be stored in % \var{l_PARC_fb_fp}. Stop condition is $|f(b)|< \epsilon$, with % $\epsilon$ stored in \texttt{\#4}. % \begin{macrocode} \fp_while_do:nNnn { abs(\l_PARC_fb_fp) } > { #4 } { % \end{macrocode} % Computing $\Delta$ and $c$ as in \eqref{eq:c}. % \begin{macrocode} \fp_set:Nn \l_PARC_delta_fp { (\l_PARC_b_fp - \l_PARC_a_fp) / (\l_PARC_fb_fp - \l_PARC_fa_fp) } \fp_set:Nn \l_PARC_c_fp { \l_PARC_b_fp - (\l_PARC_fb_fp) * (\l_PARC_delta_fp) } % \end{macrocode} % Compute $f(c)$ and store it in \var{l_PARC_fc_fp}. % \begin{macrocode} \bool_if:NTF \g_PARC_int_bool { \tl_set:Ne #1 { \fp_to_int:N \l_PARC_c_fp } } { \tl_set:Ne #1 { \fp_to_decimal:N \l_PARC_c_fp } } #7 \fp_set:Nn \l_PARC_fc_fp { #6 - (#5) } % \end{macrocode} % Now implement Illinois iteraction from \eqref{eq:iter1} % \begin{macrocode} \fp_compare:nNnTF { (\l_PARC_fc_fp) * (\l_PARC_fc_fp) } < { 0 } { \fp_set:Nn \l_PARC_a_fp { \l_PARC_b_fp } \fp_set:Nn \l_PARC_fa_fp { \l_PARC_fb_fp } } { \fp_set:Nn \l_PARC_fa_fp { 0.5 * (\l_PARC_fa_fp) } } % \end{macrocode} % and \eqref{eq:iter2}. % \begin{macrocode} \fp_set:Nn \l_PARC_b_fp { \l_PARC_c_fp } \fp_set:Nn \l_PARC_fb_fp { \l_PARC_fc_fp } } % \end{macrocode} % % Handling possible divergence if $f(a)f(b)>0$. If it doesn't converge % after \texttt{\string\l\_PARC\_maxsteps\_int} steps, an error is % signaled. % \begin{macrocode} \int_incr:N \l_PARC_steps_int \int_compare:nNnT { \l_PARC_steps_int } > { \l_PARC_maxsteps_int } { \msg_error:nn {paramcalc} { Failed ~to ~converge ~after ~\int_use:N \l_PARC_steps_int ~steps.\\ Try ~to ~narrow ~initial ~values ~around ~the ~zero. } } } % \end{macrocode} % Recall that the user access the computed value from \texttt{\#1}. % % Now we define user functions that set \var{g_PARC_int_bool} and % call previous function. % % Define \texttt{\string\paramcalc}. % \begin{macrocode} \NewDocumentCommand{\paramcalc}{ O{\x} m m O{0.05} m O{\y} m} { \bool_set:Nn \g_PARC_int_bool { \c_false_bool } \PARC_paramcalc:NnnnnNn #1 { #2 } { #3 } { #4 } { #5 } #6 { #7 } } % % Define \texttt{\string\intparamcalc}. % \begin{macrocode} \NewDocumentCommand{\intparamcalc}{ O{\x} m m O{0.05} m O{\y} m} { \bool_set:Nn \g_PARC_int_bool { \c_true_bool } \PARC_paramcalc:NnnnnNn #1 { #2 } { #3 } { #4 } { #5 } #6 { #7 } } % \end{macrocode} % % Finish \LaTeX3 syntax. % \begin{macrocode} \ExplSyntaxOff % \end{macrocode} % % \Finale % \endinput % %% \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 \~}