% \iffalse meta-comment % % numodel.dtx % Copyright (C) 2026 Paul Zuurbier % % 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 https://www.latex-project.org/lppl.txt % % This work has the LPPL maintenance status 'maintained'. % The Current Maintainer of this work is Paul Zuurbier. % % This work consists of the files numodel.dtx and numodel.ins, the % derived files numodel.sty, numodel-EN.def and numodel-NL.def, and the % companion Lua module numodel.lua. % %<*driver> \ProvidesFile{numodel.dtx}[2026/05/17 v0.3.0 numodel documentation] \documentclass{ltxdoc} \usepackage{numodel} \usepackage[left=3.5cm, right=2cm, top=2cm, bottom=2cm, marginparwidth=3.5cm, marginparsep=0.3cm]{geometry} \EnableCrossrefs \CodelineIndex \RecordChanges \begin{document} \DocInput{numodel.dtx} \end{document} % % % \fi % % \CheckSum{0} % % \changes{v0.1}{2026/04/24}{Initial version, extracted from internal % project sources.} % \changes{v0.2.0}{2026/05/16}{l3build workflow; bundle structure % with numodel-plot; diagram-style key; units key; localised % column titles; decimal-separator key; factor-aware flow % detection. See CHANGELOG.md for details.} % \changes{v0.3.0}{2026/05/17}{Version-sync release with % \textsf{numodel-plot}~v0.3.0; no functional changes to % \textsf{numodel} itself.} % % \GetFileInfo{numodel.dtx} % % \DoNotIndex{\newcommand,\newenvironment,\def,\edef,\let,\global, % \RequirePackage,\ProvidesPackage,\NeedsTeXFormat,\endinput, % \ExplSyntaxOn,\ExplSyntaxOff,\begin,\end,\relax,\undefined,\cs_new, % \cs_set,\cs_gset,\tl_new,\tl_set,\int_new,\seq_new,\prop_new} % % \title{The \textsf{numodel} package\thanks{This document corresponds % to \textsf{numodel}~\fileversion, dated \today.}} % \author{Paul Zuurbier \\ \texttt{mail@paulzuurbier.nl}} % \date{\today} % \maketitle % % \begin{abstract} % A LuaLaTeX package for writing and rendering numerical models % (Euler-integrated dynamical systems) directly inside LaTeX % documents, aimed at physics teaching material. It provides a % text-model pipeline (\texttt{\textbackslash mvar}, % \texttt{\textbackslash mrule}, \texttt{\textbackslash computemodel}), % Forrester stock-and-flow diagrams, and optional plots of the % computed time series via the sibling package \textsf{numodel-plot}. % \end{abstract} % % \tableofcontents % % \section{Introduction} % % \textsf{numodel} lets an author write a dynamical system (stocks, % flows, helper variables, rules, and a stop condition) as a sequence % of LaTeX macros and renders three complementary views of that model % directly in the document: % \begin{itemize} % \item a \emph{text model} \textemdash{} a typeset rule table with % initial values (|\textmodel|); % \item a \emph{graphic model} \textemdash{} a Forrester % stock-and-flow diagram with auto-layout (|\graphicmodel|); % \item a \emph{diagram} \textemdash{} a numerical Euler simulation % plus PGFPlot of any pair of variables (|\computemodel| followed % by |\diagrammodel|; the plot is rendered through the sibling % package \textsf{numodel-plot}). % \end{itemize} % % All three views are produced from a single set of declarations so % the textbook description, the conceptual stock-and-flow diagram, % and the numerical result of the same model are guaranteed to stay % in sync. Variables and rules live in namespaces (\emph{prefixes}) % so a document can contain multiple independent models; % |\newmodelprefix{P}| starts a fresh one. The simulation engine % runs in Lua (through \textsf{luacode}) for $\mathcal{O}(1)$ appends % and cheap min/max tracking; the rendering layer is pure % \textsf{expl3}. % % \section{First example: a free-falling ball} % % A ball dropped from $h_0 = 100\,\mathrm{m}$ under constant % gravitational acceleration. The complete model: % % \begin{quote} % \begin{verbatim} % \usepackage[syntax=EN]{numodel} % % \newmodelprefix{ball} % \mvar{T}{t}{0}{\s}{2}{system} % \mvar{Dt}{dt}{0.1}{\s}{2}{system} % \mvar{V}{v}{0}{\m\per\s}{2}{stock} % \mvar{Y}{y}{100}{\m}{3}{stock} % \mvar{G}{g}{-9.81}{\m\per\s\squared}{3}{aux} % % \mrule{V}{\ballV + \ballG * \ballDt} % \mrule{Y}{\ballY + \ballV * \ballDt} % \mrule{T}{\ballT + \ballDt} % \mstop{\ballY <= 0} % \end{verbatim} % \end{quote} % % After the declarations above, three render commands produce the % three views shown below, each from the \emph{same} model. % % \subsection{\texttt{\textbackslash textmodel} \textemdash{} rule table} % % The verbatim source rendered by |\textmodel|: % \newmodelprefix{ball} % \mvar{T}{t}{0}{\s}{2}{system} % \mvar{Dt}{dt}{0.1}{\s}{2}{system} % \mvar{V}{v}{0}{\m\per\s}{2}{stock} % \mvar{Y}{y}{100}{\m}{3}{stock} % \mvar{G}{g}{-9.81}{\m\per\s\squared}{3}{aux} % \mrule{V}{\ballV + \ballG * \ballDt} % \mrule{Y}{\ballY + \ballV * \ballDt} % \mrule{T}{\ballT + \ballDt} % \mstop{\ballY <= 0} % % \begin{center} % \textmodel % \end{center} % % Each |\mvar| with a non-empty start value contributes a row in the % \emph{initial values} column; each |\mrule| contributes a row in the % \emph{model} column. Symbols come from the second |\mvar| % argument (the display text), values are formatted through % \textsf{siunitx}. |<=| is rendered as $\leqslant$. % % \subsection{\texttt{\textbackslash graphicmodel} \textemdash{} % Forrester diagram} % % |\graphicmodel| draws the same model as a stock-and-flow diagram. % Stocks (type |stock|) are rectangles, constants (|constant|) % are circles, helpers (|aux|) are unboxed identifiers; flows are % inferred from rule structure (|\ + ...| or |... + \| % on the right-hand side): % % \begin{center} % \graphicmodel % \end{center} % % Layout is automatic from the rule graph. Manual placement is % available through |gridx|/|gridy| keys on the |\mvar| call (see % Section~\ref{sec:api}). Wide diagrams can be capped to a maximum % number of grid columns with |\numodelsetup{gridmaxx=N}|: when a row % reaches |N|, the auto layout shifts the affected items up one row % and continues filling. % % \subsection{\texttt{\textbackslash computemodel} + % \texttt{\textbackslash diagrammodel} \textemdash{} numerical plot} % % |\computemodel| iterates the rules forward in time using % Euler integration with step size~|\ballDt|, stopping when |\mstop|'s % condition becomes true (or when the |maxiter| safety limit is % reached, see Section~\ref{sec:cfg}). |\diagrammodel{xvar}{yvar}{label}| % then plots one variable against another: % \computemodel % % \begin{quote} % \begin{verbatim} % \computemodel % \diagrammodel{T}{Y}{ball-fall} % \end{verbatim} % \end{quote} % % \diagrammodel{T}{Y}{intro-ball-fall} % % Axis ranges, tick lattice, and labels are computed automatically % from the simulated min/max of each variable; the plot inherits the % \textsf{numodel-plot} style (see that package's documentation for % configuration). % % \section{Configuration}\label{sec:cfg} % % \DescribeMacro{\numodelsetup} % Runtime configuration: % \begin{quote} % \begin{verbatim} % \numodelsetup{syntax=NL, maxiter=50000} % \end{verbatim} % \end{quote} % The same keys can also be passed as package options: % |\usepackage[syntax=NL]{numodel}|. Recognised keys: % % \begin{description} % \item[\texttt{syntax}] Language tag for the rule-table rendering. % Built-in values: % \begin{description} % \item[\texttt{EN}] (default) XMILE-style ALL-CAPS keywords: % |IF|/|THEN|/|ELSE|, |AND|, |OR|, |ABS|, |SIGN|, \ldots % \item[\texttt{NL}] Dutch CoachTaal keywords: % |Als|/|Dan|/|Anders|, |EN|, |OF|, |Abs|, |Teken|, \ldots % \end{description} % The legacy names |english|, |coachtaal| and |dutch| are accepted % as aliases for |EN| and |NL|. Each language tag |X| corresponds % to a file |numodel-X.def| located via |kpse| when the package % processes the key. The package ships with |numodel-EN.def| and % |numodel-NL.def|; drop your own |numodel-FR.def| (or any other % tag) in |TEXMFHOME/tex/latex/numodel/| and select it with % |\usepackage[syntax=FR]{numodel}| -- no package rebuild needed. % The setting affects display only; the expression syntax in % |\mrule| bodies is always |\fp_eval|-compatible. % \item[\texttt{maxiter}] Safety limit on the number of |\computemodel| % iterations (default 20\,000). When reached, the simulation % aborts with a warning naming the unmet stop condition. % \item[\texttt{graphscalex}] Horizontal grid spacing in centimetres % for |\graphicmodel|'s Forrester layout (default 2). Larger % values spread the diagram out horizontally. % \item[\texttt{graphscaley}] Vertical grid spacing in centimetres % for |\graphicmodel|'s Forrester layout (default 2). Larger % values spread the diagram out vertically. % \item[\texttt{stockwidth}] Half-width of stock rectangles in % |\graphicmodel| (default 0.375). % \item[\texttt{gridmaxx}] Maximum number of grid columns the auto % layout may fill on any one row before wrapping (integer, default % |0| = no limit). When the limit is reached, items already placed % on the affected row (and everything above it for the stocks row, % everything but stocks for the aux row, only the constants for the % constants row) shift up by one row to free space, and placement % continues from column~0. Manually positioned variables % (|\mvar[gridx=...,gridy=...]|) are kept where they are. When % wrapping is active the default centring of the aux row and the % right-aligning of the stocks row are disabled, so the diagram fills % left-to-right, bottom-to-top. % \item[\texttt{diagram-style}] Rendering style for the case where a % helper or constant is the direct inflow/outflow of a stock. % Three values: % \begin{description} % \item[\texttt{tight}] (default) the valve takes the helper's/constant's % label; the helper/constant itself is not drawn as a separate node. % Compact and most LaTeX-native. % \item[\texttt{forrester}] Forrester/Sterman convention: the valve is % drawn without a label and the helper/constant remains as a separate % node connected to the valve by a causal arrow. % \item[\texttt{edu}] Didactic dual form: the valve carries the label % \emph{and} the helper/constant is drawn as a separate node with a % causal arrow to the valve. Visually busy but pedagogically explicit. % \end{description} % \item[\texttt{flowarrow-style}] Visual style of the flow pipe. % |hollow| renders the classic Forrester double-line pipe with an % open arrow head; |filled| renders a thick solid arrow. The % default tracks |diagram-style|: |forrester| picks |hollow|, the % other styles pick |filled|. An explicit value overrides this % coupling. % \item[\texttt{valve-style}] Visual style of the valve node. % |valve| draws the bow-tie/butterfly icon (Forrester); |circle| % draws an empty circle on the flow pipe; |edu| draws a labelled % circle (the flow variable's display text inside). The default % tracks |diagram-style|: |forrester| picks |valve|, the other % styles pick |edu|. % \item[\texttt{flowarrow-cloud-tip}] Whether the open end of an % inflow or outflow pipe is anchored to a cloud node, signalling % the model boundary. Default tracks |diagram-style|: |forrester| % picks |true|, the other styles pick |false|. May be set % globally via |\numodelsetup|, per-render via |\graphicmodel|, or % per-stock via |\mvar[flowarrow-cloud-tip=...]|. The most % specific source wins. % \item[\texttt{units}] Whether the \emph{initial values} cells in % |\textmodel| display the SI unit alongside the value (|\qty|) or % only the numeric value (|\num|). Boolean, default |true|. May % also be supplied to |\textmodel[units=false]| as a per-table % override; the global setting is restored after rendering. % \item[\texttt{decimal-separator}] Decimal mark used by every number % that |numodel| renders: the \emph{initial values} column of % |\textmodel|, and the tick labels of |\diagrammodel|. Two values: % \begin{description} % \item[\texttt{comma}] use a comma (\textsf{siunitx} % |output-decimal-marker={,}|, \textsf{pgfplots} % |/pgf/number format/use comma|). % \item[\texttt{point}] use a full stop (\textsf{siunitx} % |output-decimal-marker={.}|, \textsf{pgfplots} % |/pgf/number format/use period|). % \end{description} % The default tracks |syntax|: |NL| picks |comma|, |EN| picks % |point|. Other language files can publish a default by % defining |\__numodel_kw__dsep_default:| (expanding to % |point| or |comma|); when the macro is absent the default is % |point|. An explicit |decimal-separator| key locks that % choice and overrides any future |syntax| change. The override is % scoped: |numodel| applies it only inside its own renderers % (\textsf{siunitx}'s state is restored on group exit), so a % document-wide |\sisetup| is not perturbed. % \end{description} % % \subsection{Diagram styles in practice} % % The same model rendered under each of the three |diagram-style| % values. The model is the simplest case that distinguishes the % styles: one stock $N$ with a constant inflow~$R$: % \begin{quote} % \begin{verbatim} % \newmodelprefix{flux} % \mvar{T}{t}{0}{\s}{2}{system} % \mvar{Dt}{dt}{1}{\s}{2}{system} % \mvar{N}{n}{0}{}{0}{stock} % \mvar{R}{r}{5}{\per\s}{2}{constant} % \mrule{N}{\fluxN + \fluxR * \fluxDt} % \mrule{T}{\fluxT + \fluxDt} % \mstop{\fluxT >= 5} % \graphicmodel[diagram-style=tight] % \graphicmodel[diagram-style=forrester] % \graphicmodel[diagram-style=edu] % \end{verbatim} % \end{quote} % % \newmodelprefix{flux} % \mvar{T}{t}{0}{\s}{2}{system} % \mvar{Dt}{dt}{1}{\s}{2}{system} % \mvar{N}{n}{0}{}{0}{stock} % \mvar{R}{r}{5}{\per\s}{2}{constant} % \mrule{N}{\fluxN + \fluxR * \fluxDt} % \mrule{T}{\fluxT + \fluxDt} % \mstop{\fluxT >= 5} % % \begin{center} % \begin{tabular}{@{}ccc@{}} % \graphicmodel[diagram-style=tight] & % \graphicmodel[diagram-style=forrester] & % \graphicmodel[diagram-style=edu] \\[2pt] % \texttt{tight} & \texttt{forrester} & \texttt{edu} % \end{tabular} % \end{center} % % \begin{itemize} % \item \texttt{tight} collapses the constant $R$ into the valve % label, producing the most compact diagram. % \item \texttt{forrester} keeps the canonical System-Dynamics % convention: unlabelled bow-tie valve, the constant remains a % separate node, the link from $R$ to the valve is a thin causal % arrow. % \item \texttt{edu} is a didactic dual: the valve carries the label % \emph{and} the constant remains as a separate node with a causal % arrow. Less compact but pedagogically explicit -- useful when % first introducing the stock/flow vocabulary. % \end{itemize} % % \section{Public API}\label{sec:api} % % \subsection{Variables and rules} % % \DescribeMacro{\mvar} % Declares a model variable. Signature: % \begin{quote} % |\mvar[]{}{}{}{}{}{}| % \end{quote} % where \meta{Name} is a short alphabetic identifier (the % prefix-qualified accessor becomes |\|), \meta{text} % is the math-mode display symbol used in the rule table and % diagram (e.g.\ |F_{res}|), \meta{start} is the initial value % (a number, or empty for helpers computed by a rule, or any % \textsf{expl3} \texttt{fp}-evaluable expression involving previously % defined model variables), \meta{unit} is a bare \textsf{siunitx} % unit macro sequence (e.g.\ |\m\per\s\squared|), \meta{sig} is the % number of significant figures used by |\num|/|qty|, % and \meta{type} is one of |stock|, |aux|, |constant|, or % |system|. Each English type also accepts a Dutch alias % (|voorraad|, |hulp|, |constante|, |systeem|) for backwards % compatibility with existing teaching material. See % Section~\ref{sec:types}. % % Optional \meta{keys}: % \begin{description} % \item[\texttt{prefix}] Override the current prefix for this single % call. % \item[\texttt{gridx}, \texttt{gridy}] Manual placement in the % |\graphicmodel| grid; integers, $-1$ leaves the slot to % auto-layout (default). % \item[\texttt{alias}] Math-mode token list that replaces the % entire \emph{initial values} cell. % \item[\texttt{aliasleft}, \texttt{aliasright}] Replace just the % left symbol or right value half of the cell. % \end{description} % % \DescribeMacro{\mrule} % Adds a rule of the form $\meta{LHS} \leftarrow \meta{expr}$. % Signature: % \begin{quote} % |\mrule*[]{}{}| % \end{quote} % Both forms add the rule to \emph{both} the rule table % (|\textmodel|) and the simulation (|\computemodel|); execution is % identical. The star only changes the typeset layout when % \meta{expr} is a ternary |cond ? a : b|. Without the star the % ternary is rendered on a single table row, % \begin{quote} % \begin{verbatim} % IF cond THEN lhs = a ELSE lhs = b ENDIF % \end{verbatim} % \end{quote} % which is compact but wide. With the star (|\mrule*|) the same % ternary is broken across rows, % \begin{quote} % \begin{verbatim} % IF cond THEN % lhs = a % ELSE % lhs = b % ENDIF % \end{verbatim} % \end{quote} % keeping the table column narrow so a |\graphicmodel| can sit % alongside it, and making the source itself easier to read. For % non-ternary expressions the star has no effect. % \meta{expr} may use the full |\fp_eval| expression grammar % including |+ - * / ^|, |abs|, |sign|, ternary |cond ? a : b|, and % Boolean operators \texttt{\&\&} (and) and \texttt{||} (or), and the % comparison operators \texttt{<}, \texttt{<=}, \texttt{>}, \texttt{>=}, % \texttt{=}, \texttt{!=}. % % \DescribeMacro{\mruletext} % Inserts a free-text row in the rule table without registering a % rule with the simulator. Signature: |\mruletext[]{}|. % Useful for inserting comments or section dividers in long % rule tables. % % \DescribeMacro{\mstop} % Sets the simulation stop condition. Signature: % |\mstop[]{}|. The simulation halts at the first step % where \meta{expr} evaluates true. Exactly one |\mstop| per model % prefix is required before |\computemodel|. Without one, % |\computemodel| issues a warning. % % \subsection{Render commands} % % \DescribeMacro{\textmodel} % Renders the rule-and-startvalue table. Optional |[]| accepts % |prefix=| (render a non-current model) and either % |units=true| or |units=false|, which overrides the global % |\numodelsetup| setting for this single render only. The global % state is restored afterwards. % % \DescribeMacro{\graphicmodel} % Renders the Forrester stock-and-flow diagram. Variables of type % |stock| become rectangles, |constant| become circles, |aux| % become identifier nodes; flow arrows connect stocks to constant or % helper sources/sinks based on which variables appear in % the right-hand side of stock-updating rules. % % Optional |[]| accepts |prefix=| (render a non-current % model) and |diagram-style=tight|forrester|edu|, which overrides the % global |\numodelsetup| setting for this single render only. The % global state is restored afterwards, so multiple |\graphicmodel| % calls can each pick their own style without re-issuing % |\numodelsetup|. % % \DescribeMacro{\computemodel} % Runs the Euler simulation in Lua. Records every variable's value % at every step, plus running min and max. After it returns, the % accessors |\min| / |\max| hold the % extrema, and |\| holds the final-step value. The % time-series can be retrieved with |\mcoords| / |\mstep|. % % \DescribeMacro{\diagrammodel} % Convenience wrapper that produces a complete |figure| with caption % and label. Signature: % \begin{quote} % |\diagrammodel[]{}{}[]{