LINE Solver
MATLAB API documentation
Loading...
Searching...
No Matches
exportODEs.m
1function [tex, sys] = exportODEs(self, filename, notation)
2% [TEX, SYS] = EXPORTODES(FILENAME, NOTATION)
3% Export the system of ODEs integrated by the mean-field methods of
4% SolverFLD as a standalone LaTeX document, in a symbolic form that is
5% both human and machine readable. The exported system corresponds to
6% the solver method set in the solver options (default/matrix, pnorm,
7% closing, statedep, softmin).
8%
9% Input:
10% filename - path of the .tex file to write; if empty or omitted the
11% LaTeX source is returned without writing a file
12% notation - 'scalar' (default): one expanded ODE per state variable
13% 'matrix': compact matrix notation, dx/dt = W'*theta(x) +
14% lambda for the matrix/pnorm methods and dx/dt = J*r(x) for
15% the closing/statedep/softmin methods
16%
17% Output:
18% tex - LaTeX source (char)
19% sys - structural description of the ODE system (see solver_fluid_symodes)
20%
21% The document header also carries the state-variable mapping as LaTeX
22% comments (lines starting with % STATE) for machine parsing.
23%
24% Copyright (c) 2012-2026, Imperial College London
25% All rights reserved.
26
27if nargin < 2
28 filename = '';
29end
30if nargin < 3
31 notation = 'scalar';
32end
33switch notation
34 case {'scalar','matrix'}
35 % valid
36 otherwise
37 line_error(mfilename, sprintf('Unknown notation ''%s''. Valid notations: scalar, matrix.', notation));
38end
39
40options = self.getOptions;
41sn = self.getStruct;
42sn = sn_nonmarkov_toph(sn, options);
43sys = solver_fluid_symodes(sn, options);
44sys.x0 = build_x0(sys, sn, options);
45
46modelName = self.model.getName;
47n = sys.nstates;
48
49%% header comments (machine readable)
50L = {};
51L{end+1} = '% Mean-field fluid ODE system exported by LINE SolverFLD';
52L{end+1} = sprintf('%% model: %s', modelName);
53L{end+1} = sprintf('%% method: %s', sys.method);
54switch sys.form
55 case 'W'
56 L{end+1} = '% form: dx/dt = W^T*theta(x) + lambda';
57 case 'J'
58 L{end+1} = '% form: dx/dt = J*r(x)';
59end
60L{end+1} = sprintf('%% notation: %s', notation);
61L{end+1} = sprintf('%% nstates: %d', n);
62if strcmp(sys.form,'J')
63 L{end+1} = sprintf('%% nevents: %d', sys.nevents);
64end
65for s = 1:n
66 L{end+1} = sprintf('%% STATE %d station=%s class=%s phase=%d', s, ...
67 sys.stationNames{sys.stateStation(s)}, sys.classNames{sys.stateClass(s)}, sys.statePhase(s)); %#ok<AGROW>
68end
69if strcmp(sys.form,'J')
70 for e = 1:sys.nevents
71 L{end+1} = sprintf('%% EVENT %d var=%d type=%s coeff=%.15g', e, ...
72 sys.eventVar(e), sys.factorType{e}, sys.coeff(e)); %#ok<AGROW>
73 end
74end
75
76%% preamble
77L{end+1} = '\documentclass{article}';
78L{end+1} = '\usepackage{amsmath}';
79L{end+1} = '\usepackage[margin=2.5cm]{geometry}';
80L{end+1} = '\allowdisplaybreaks';
81L{end+1} = '\setcounter{MaxMatrixCols}{500}';
82L{end+1} = '\begin{document}';
83L{end+1} = '\section*{Mean-field fluid ODE system}';
84L{end+1} = sprintf('\\noindent Model: \\texttt{%s}. Solver: \\texttt{SolverFLD}, method \\texttt{%s}, %s notation.', ...
85 texesc(modelName), texesc(sys.method), notation);
86switch sys.form
87 case 'W'
88 L{end+1} = sprintf('The system has %d state variables and reads $\\frac{\\mathrm{d}\\mathbf{x}}{\\mathrm{d}t} = W^{\\top}\\theta(\\mathbf{x}) + \\boldsymbol{\\lambda}$.', n);
89 case 'J'
90 L{end+1} = sprintf('The system has %d state variables and %d events and reads $\\frac{\\mathrm{d}\\mathbf{x}}{\\mathrm{d}t} = J\\,r(\\mathbf{x})$.', n, sys.nevents);
91end
92
93%% state variable legend
94L{end+1} = '\subsection*{State variables}';
95L{end+1} = 'Each state variable $x_{s}$ is the mean number of jobs of a class in a service phase at a station:';
96L{end+1} = '\begin{center}';
97chunk = 48;
98for s0 = 1:chunk:n
99 s1 = min(n, s0+chunk-1);
100 L{end+1} = '\begin{tabular}{rlll}'; %#ok<AGROW>
101 L{end+1} = '\hline'; %#ok<AGROW>
102 L{end+1} = '$s$ & station & class & phase\\'; %#ok<AGROW>
103 L{end+1} = '\hline'; %#ok<AGROW>
104 for s = s0:s1
105 L{end+1} = sprintf('%d & \\texttt{%s} & \\texttt{%s} & %d\\\\', s, ...
106 texesc(sys.stationNames{sys.stateStation(s)}), ...
107 texesc(sys.classNames{sys.stateClass(s)}), sys.statePhase(s)); %#ok<AGROW>
108 end
109 L{end+1} = '\hline'; %#ok<AGROW>
110 L{end+1} = '\end{tabular}'; %#ok<AGROW>
111 if s1 < n
112 L{end+1} = '\par\medskip'; %#ok<AGROW>
113 end
114end
115L{end+1} = '\end{center}';
116
117% station legend
118usedStations = unique(sys.stateStation(:))';
119L{end+1} = '\begin{center}';
120L{end+1} = '\begin{tabular}{rlll}';
121L{end+1} = '\hline';
122L{end+1} = '$i$ & station & scheduling & $S_{i}$\\';
123L{end+1} = '\hline';
124for i = usedStations
125 L{end+1} = sprintf('%d & \\texttt{%s} & %s & $%s$\\\\', i, ...
126 texesc(sys.stationNames{i}), texesc(sys.schedNames{i}), fmtnum(sys.S(i))); %#ok<AGROW>
127end
128L{end+1} = '\hline';
129L{end+1} = '\end{tabular}';
130L{end+1} = '\end{center}';
131
132%% definitions of station-level auxiliary functions
133[T, varFactor, constTerm] = build_terms(sys);
134defs = build_defs(sys, varFactor);
135if ~isempty(defs)
136 L{end+1} = '\subsection*{Definitions}';
137 L{end+1} = '\begin{align*}';
138 for d = 1:length(defs)
139 L{end+1} = defs{d}; %#ok<AGROW>
140 end
141 L{end+1} = '\end{align*}';
142end
143
144%% ODE system
145switch notation
146 case 'scalar'
147 L{end+1} = '\subsection*{ODE system (scalar notation)}';
148 L{end+1} = '\begin{align}';
149 for s = 1:n
150 L{end+1} = render_equation(sys, s, T, varFactor, constTerm, s==n); %#ok<AGROW>
151 end
152 L{end+1} = '\end{align}';
153 case 'matrix'
154 L{end+1} = '\subsection*{ODE system (matrix notation)}';
155 switch sys.form
156 case 'W'
157 haveLambda = any(sys.Alambda ~= 0);
158 if haveLambda
159 L{end+1} = '\begin{equation}';
160 L{end+1} = '\frac{\mathrm{d}\mathbf{x}}{\mathrm{d}t} = W^{\top}\,\theta(\mathbf{x}) + \boldsymbol{\lambda}';
161 L{end+1} = '\end{equation}';
162 else
163 L{end+1} = '\begin{equation}';
164 L{end+1} = '\frac{\mathrm{d}\mathbf{x}}{\mathrm{d}t} = W^{\top}\,\theta(\mathbf{x})';
165 L{end+1} = '\end{equation}';
166 end
167 L{end+1} = 'with $\theta_{s}(\mathbf{x})$ given componentwise by';
168 L{end+1} = '\begin{equation*}';
169 L{end+1} = ['\theta(\mathbf{x}) = ', render_theta_vector(sys, varFactor)];
170 L{end+1} = '\end{equation*}';
171 L{end+1} = 'and';
172 L{end+1} = '\begin{equation*}';
173 L{end+1} = ['W^{\top} = ', render_num_matrix(sys.W')];
174 L{end+1} = '\end{equation*}';
175 if haveLambda
176 L{end+1} = '\begin{equation*}';
177 L{end+1} = ['\boldsymbol{\lambda} = ', render_num_vector(sys.Alambda), '^{\top}'];
178 L{end+1} = '\end{equation*}';
179 end
180 case 'J'
181 L{end+1} = '\begin{equation}';
182 L{end+1} = '\frac{\mathrm{d}\mathbf{x}}{\mathrm{d}t} = J\,r(\mathbf{x})';
183 L{end+1} = '\end{equation}';
184 L{end+1} = 'with stoichiometry matrix';
185 L{end+1} = '\begin{equation*}';
186 L{end+1} = ['J = ', render_num_matrix(sys.J)];
187 L{end+1} = '\end{equation*}';
188 L{end+1} = 'and event rate functions';
189 L{end+1} = '\begin{align*}';
190 for e = 1:sys.nevents
191 fd = sys.factorData{e};
192 fstr = factor_tex(sys.eventVar(e), sys.factorType{e}, fd);
193 L{end+1} = sprintf('r_{%d}(\\mathbf{x}) &= %s%s', e, ...
194 term_tex(sys.coeff(e), fstr), tern(e<sys.nevents,'\\','')); %#ok<AGROW>
195 end
196 L{end+1} = '\end{align*}';
197 end
198end
199
200%% initial condition
201if ~isempty(sys.x0)
202 L{end+1} = '\subsection*{Initial condition}';
203 L{end+1} = '\begin{equation*}';
204 L{end+1} = ['\mathbf{x}(0) = ', render_num_vector(sys.x0), '^{\top}'];
205 L{end+1} = '\end{equation*}';
206end
207
208%% remarks
209L{end+1} = '\subsection*{Remarks}';
210L{end+1} = '\begin{itemize}';
211L{end+1} = sprintf('\\item For each station $i$, $n_{i}(\\mathbf{x})$ denotes the total mass at the station and $S_{i}$ the number of servers (infinite-server stations use the closed job population, $%s$ denotes infinity).', '\infty');
212L{end+1} = '\item The numerical solver regularizes vanishing denominators with a small positive constant; these regularizations are omitted here.';
213if strcmp(sys.form,'J') && any(strcmp('fcfsw', sys.factorType) | strcmp('fcfsws', sys.factorType))
214 L{end+1} = '\item At FCFS stations, the mean phase residence times $w_{u} = -1/[D_{0}]_{kk}$ weight the backlog $\hat{n}_{i}$; the factors $w_{u}$ of the departing phases are folded into the rate coefficients.';
215end
216if strcmp(sys.form,'J') && any(strcmp('dps', sys.factorType))
217 L{end+1} = '\item At DPS stations, weights are normalized to sum to one and the products $S_{i} w_{ir}$ are folded into the rate coefficients; the constant added to $\tilde{n}_{i}$ in the denominator mirrors the implementation in \texttt{ode\_rates\_closing}.';
218end
219if any(sys.sched(unique(sys.stateStation)) == SchedStrategy.FCFS) && any(strcmp(sys.method, {'matrix','closing'}))
220 L{end+1} = '\item For FCFS stations with non-exponential service, the solver may iteratively re-fit the service distributions (non-exponential approximation); the exported system uses the nominal model parameters.';
221end
222if isfield(options.config,'hide_immediate') && options.config.hide_immediate
223 L{end+1} = '\item \texttt{hide\_immediate} is enabled in the solver options: the numerical integration may further eliminate immediate transitions by state-space reduction; the exported system is the unreduced one.';
224end
225L{end+1} = '\end{itemize}';
226L{end+1} = '\end{document}';
227
228tex = strjoin(L, newline);
229tex = sprintf('%s\n', tex);
230
231if ~isempty(filename)
232 fid = fopen(filename, 'w');
233 if fid == -1
234 line_error(mfilename, sprintf('Cannot open file ''%s'' for writing.', filename));
235 end
236 fprintf(fid, '%s', tex);
237 fclose(fid);
238end
239end
240
241%% helpers
242
243function x0 = build_x0(sys, sn, options)
244% initial condition in the exported state space
245init_sol = solver_fluid_initsol(sn, options);
246init_sol = init_sol(:);
247switch sys.form
248 case 'J'
249 x0 = init_sol;
250 case 'W'
251 M = sn.nstations;
252 K = sn.nclasses;
253 nphases = sn.phases;
254 x0_build = [];
255 state = 0;
256 init_idx = 0;
257 for ist = 1:M
258 for r = 1:K
259 if nphases(ist,r) == 0
260 state = state + 1;
261 x0_build(state,1) = 0; %#ok<AGROW>
262 else
263 for k = 1:nphases(ist,r)
264 state = state + 1;
265 if isnan(sn.rates(ist,r))
266 x0_build(state,1) = 0; %#ok<AGROW>
267 else
268 init_idx = init_idx + 1;
269 x0_build(state,1) = init_sol(init_idx); %#ok<AGROW>
270 end
271 end
272 end
273 end
274 end
275 x0 = x0_build(sys.keep);
276 x0(sys.isSource) = 0;
277end
278end
279
280function [T, varFactor, constTerm] = build_terms(sys)
281% T(s,v): constant coefficient of the term driven by state variable v in
282% the equation of state s; varFactor{v}: factor descriptor of variable v;
283% constTerm(s): additive constant.
284n = sys.nstates;
285T = zeros(n,n);
286varFactor = cell(n,1);
287switch sys.form
288 case 'W'
289 T = sys.W';
290 T(:, sys.isSource) = 0; % theta of Source states is identically zero
291 for v = 1:n
292 if ~sys.isSource(v)
293 i = sys.stateStation(v);
294 if isinf(sys.S(i))
295 varFactor{v} = struct('type','lin','station',i,'class',sys.stateClass(v));
296 else
297 varFactor{v} = struct('type',sys.smoothing,'station',i,'class',sys.stateClass(v));
298 end
299 end
300 end
301 constTerm = sys.Alambda;
302 case 'J'
303 for e = 1:sys.nevents
304 v = sys.eventVar(e);
305 T(:,v) = T(:,v) + sys.J(:,e) * sys.coeff(e);
306 if isempty(varFactor{v})
307 fd = sys.factorData{e};
308 fd.type = sys.factorType{e};
309 varFactor{v} = fd;
310 end
311 end
312 constTerm = zeros(n,1);
313end
314end
315
316function defs = build_defs(sys, varFactor)
317% station-level auxiliary definitions used by the factors
318defs = {};
319n = sys.nstates;
320M = length(sys.stationNames);
321needN = false(M,1);
322needNT = false(M,1); % ntilde (DPS-weighted mass)
323needNH = false(M,1); % nhat (FCFS backlog)
324gdef = cell(M,1);
325for v = 1:n
326 f = varFactor{v};
327 if isempty(f)
328 continue
329 end
330 i = f.station;
331 switch f.type
332 case 'lin'
333 % no auxiliary quantity
334 case 'min'
335 needN(i) = true;
336 gdef{i} = sprintf('g_{%d}(\\mathbf{x}) &= \\frac{\\min(n_{%d}(\\mathbf{x}),\\, %s)}{n_{%d}(\\mathbf{x})}', i, i, fmtnum(sys.S(i)), i);
337 case 'pnorm'
338 needN(i) = true;
339 gdef{i} = sprintf('g_{%d}(\\mathbf{x}) &= \\Bigl(1 + \\bigl(n_{%d}(\\mathbf{x})/%s\\bigr)^{%s}\\Bigr)^{-1/%s}', ...
340 i, i, fmtnum(sys.S(i)), fmtnum(sys.pstar(i)), fmtnum(sys.pstar(i)));
341 case 'dps'
342 needNT(i) = true;
343 gdef{i} = sprintf('g_{%d}(\\mathbf{x}) &= \\frac{1}{%s + \\tilde{n}_{%d}(\\mathbf{x})}', i, fmtnum(f.c0), i);
344 case 'dpspw'
345 needN(i) = true;
346 needNT(i) = true;
347 if isempty(gdef{i})
348 gdef{i} = '';
349 end
350 % one definition per enabled class at the station, built below
351 case {'fcfsw','fcfsws'}
352 needN(i) = true;
353 needNH(i) = true;
354 if strcmp(f.type,'fcfsw')
355 gdef{i} = sprintf('g_{%d}(\\mathbf{x}) &= \\frac{\\min(n_{%d}(\\mathbf{x}),\\, %s)}{\\hat{n}_{%d}(\\mathbf{x})}', i, i, fmtnum(sys.S(i)), i);
356 else
357 gdef{i} = sprintf('g_{%d}(\\mathbf{x}) &= \\frac{\\mathrm{softmin}\\bigl(n_{%d}(\\mathbf{x}),\\, %s\\bigr)}{\\hat{n}_{%d}(\\mathbf{x})}', i, i, fmtnum(sys.S(i)), i);
358 end
359 end
360end
361% n_i definitions
362for i = 1:M
363 if needN(i)
364 vlist = find(sys.stateStation == i);
365 defs{end+1} = sprintf('n_{%d}(\\mathbf{x}) &= %s\\\\', i, strjoin(arrayfun(@(v) sprintf('x_{%d}',v), vlist(:)', 'UniformOutput', false), ' + ')); %#ok<AGROW>
366 end
367end
368% ntilde_i definitions (DPS-weighted station mass)
369for i = 1:M
370 if needNT(i)
371 parts = {};
372 K = length(sys.classNames);
373 for r = 1:K
374 vlist = find(sys.stateStation == i & sys.stateClass == r);
375 if ~isempty(vlist)
376 parts{end+1} = sprintf('%s\\,(%s)', fmtnum(sys.dpsw(i,r)), strjoin(arrayfun(@(v) sprintf('x_{%d}',v), vlist(:)', 'UniformOutput', false), ' + ')); %#ok<AGROW>
377 end
378 end
379 defs{end+1} = sprintf('\\tilde{n}_{%d}(\\mathbf{x}) &= %s\\\\', i, strjoin(parts, ' + ')); %#ok<AGROW>
380 end
381end
382% nhat_i definitions (FCFS backlog weighted by mean phase residence times)
383for i = 1:M
384 if needNH(i)
385 vlist = find(sys.stateStation == i);
386 parts = arrayfun(@(v) sprintf('%s\\,x_{%d}', fmtnum(sys.fcfsPhaseW(v)), v), vlist(:)', 'UniformOutput', false);
387 defs{end+1} = sprintf('\\hat{n}_{%d}(\\mathbf{x}) &= %s\\\\', i, strjoin(parts, ' + ')); %#ok<AGROW>
388 end
389end
390% g definitions
391for i = 1:M
392 if ~isempty(gdef{i})
393 defs{end+1} = sprintf('%s\\\\', gdef{i}); %#ok<AGROW>
394 end
395end
396% per-class piecewise DPS definitions
397for v = 1:n
398 f = varFactor{v};
399 if ~isempty(f) && strcmp(f.type,'dpspw')
400 i = f.station;
401 r = f.class;
402 d = sprintf('g_{%d,%d}(\\mathbf{x}) &= \\begin{cases} 1 & n_{%d}(\\mathbf{x}) \\le %s\\\\ \\dfrac{%s}{\\tilde{n}_{%d}(\\mathbf{x})} & n_{%d}(\\mathbf{x}) > %s \\end{cases}\\\\', ...
403 i, r, i, fmtnum(sys.S(i)), fmtnum(sys.S(i)*sys.dpsw(i,r)), i, i, fmtnum(sys.S(i)));
404 if ~any(strcmp(defs, d))
405 defs{end+1} = d; %#ok<AGROW>
406 end
407 end
408end
409% softmin definition
410if isfield(sys,'alpha') && any(cellfun(@(f) ~isempty(f) && strcmp(f.type,'fcfsws'), varFactor))
411 defs{end+1} = sprintf('\\mathrm{softmin}(a,b) &= \\frac{a\\,e^{-\\alpha a} + b\\,e^{-\\alpha b}}{e^{-\\alpha a} + e^{-\\alpha b}}, \\qquad \\alpha = %s\\\\', fmtnum(sys.alpha));
412end
413% strip the trailing line break of the last definition
414if ~isempty(defs)
415 defs{end} = regexprep(defs{end}, '\\\\$', '');
416end
417end
418
419function s = render_equation(sys, sidx, T, varFactor, constTerm, isLast)
420% one align row for the ODE of state variable sidx
421terms = {};
422for v = 1:sys.nstates
423 c = T(sidx,v);
424 if c ~= 0
425 fstr = factor_tex(v, varFactor{v}.type, varFactor{v});
426 terms{end+1} = {c, fstr}; %#ok<AGROW>
427 end
428end
429if constTerm(sidx) ~= 0
430 terms{end+1} = {constTerm(sidx), ''};
431end
432if isempty(terms)
433 rhs = '0';
434else
435 parts = {};
436 for k = 1:length(terms)
437 c = terms{k}{1};
438 body = term_tex(abs(c), terms{k}{2});
439 if k == 1
440 if c < 0
441 parts{end+1} = ['-', body]; %#ok<AGROW>
442 else
443 parts{end+1} = body; %#ok<AGROW>
444 end
445 else
446 if c < 0
447 parts{end+1} = [' - ', body]; %#ok<AGROW>
448 else
449 parts{end+1} = [' + ', body]; %#ok<AGROW>
450 end
451 end
452 % break long equations every four terms
453 if mod(k,4) == 0 && k < length(terms)
454 parts{end+1} = sprintf('\\nonumber\\\\\n&\\quad '); %#ok<AGROW>
455 end
456 end
457 rhs = strjoin(parts, '');
458end
459s = sprintf('\\frac{\\mathrm{d}x_{%d}}{\\mathrm{d}t} &= %s%s', sidx, rhs, tern(~isLast,'\\',''));
460end
461
462function fstr = factor_tex(v, ftype, fdata)
463% LaTeX of the state-dependent factor of a term driven by variable v
464switch ftype
465 case 'lin'
466 fstr = sprintf('x_{%d}', v);
467 case {'min','pnorm','dps','fcfsw','fcfsws'}
468 fstr = sprintf('x_{%d}\\,g_{%d}(\\mathbf{x})', v, fdata.station);
469 case 'dpspw'
470 fstr = sprintf('x_{%d}\\,g_{%d,%d}(\\mathbf{x})', v, fdata.station, fdata.class);
471 case 'ext1'
472 if isempty(fdata.others)
473 fstr = ''; % single-phase source class: constant unit mass
474 else
475 fstr = sprintf('\\bigl(1 - %s\\bigr)', strjoin(arrayfun(@(u) sprintf('x_{%d}',u), fdata.others(:)', 'UniformOutput', false), ' - '));
476 end
477end
478end
479
480function s = term_tex(c, fstr)
481% LaTeX of a term with positive coefficient c and factor fstr
482if isempty(fstr)
483 s = fmtnum(c);
484elseif c == 1
485 s = fstr;
486else
487 s = sprintf('%s\\,%s', fmtnum(c), fstr);
488end
489end
490
491function s = render_theta_vector(sys, varFactor)
492% componentwise theta vector for the matrix notation of the W form
493rows = cell(sys.nstates,1);
494for v = 1:sys.nstates
495 if sys.isSource(v)
496 rows{v} = '0';
497 else
498 rows{v} = factor_tex(v, varFactor{v}.type, varFactor{v});
499 end
500end
501s = sprintf('\\begin{bmatrix} %s \\end{bmatrix}', strjoin(rows, ' \\\\ '));
502end
503
504function s = render_num_matrix(A)
505% numeric matrix as a LaTeX bmatrix
506[m, ncol] = size(A); %#ok<ASGLU>
507rows = cell(m,1);
508for r = 1:m
509 rows{r} = strjoin(arrayfun(@fmtnum, A(r,:), 'UniformOutput', false), ' & ');
510end
511body = strjoin(rows, ' \\\\ ');
512if max(size(A)) > 12
513 s = sprintf('{\\scriptsize\\begin{bmatrix} %s \\end{bmatrix}}', body);
514else
515 s = sprintf('\\begin{bmatrix} %s \\end{bmatrix}', body);
516end
517end
518
519function s = render_num_vector(v)
520% numeric column vector as a transposed LaTeX row
521s = sprintf('\\begin{pmatrix} %s \\end{pmatrix}', strjoin(arrayfun(@fmtnum, v(:)', 'UniformOutput', false), ' & '));
522end
523
524function s = fmtnum(v)
525% compact LaTeX-safe number formatting
526if isinf(v)
527 if v > 0
528 s = '\infty';
529 else
530 s = '-\infty';
531 end
532elseif v == round(v) && abs(v) < 1e15
533 s = sprintf('%d', v);
534else
535 s = sprintf('%.8g', v);
536end
537end
538
539function s = texesc(s)
540% escape LaTeX special characters in identifiers
541s = regexprep(s, '([_%&#])', '\\$1');
542end
543
544function out = tern(cond, a, b)
545if cond
546 out = a;
547else
548 out = b;
549end
550end
Definition Station.m:245