File Coverage

blib/lib/Math/Symbolic/Custom/LaTeXDumper.pm
Criterion Covered Total %
statement 92 125 73.6
branch 30 60 50.0
condition 23 52 44.2
subroutine 18 36 50.0
pod 1 1 100.0
total 164 274 59.8


line stmt bran cond sub pod time code
1              
2             =encoding utf8
3              
4             =head1 NAME
5              
6             Math::Symbolic::Custom::LaTeXDumper - Math::Symbolic LaTeX output
7              
8             =head1 SYNOPSIS
9              
10             use Math::Symbolic qw/parse_from_string/;
11             use Math::Symbolic::Custom::LaTeXDumper;
12             $term = parse_from_string(...);
13             print $term->to_latex(...);
14              
15             =head1 DESCRIPTION
16              
17             This class provides the C method for all L
18             trees. It is a rewrite of the C method that was supplied by
19             C prior to version 0.201.
20              
21             For details on how the custom method delegation model works, please have
22             a look at the C and C
23             classes.
24              
25             =head2 EXPORT
26              
27             Please see the docs for C for details, but
28             you should not try to use the standard Exporter semantics with this
29             class.
30              
31             =head1 SUBROUTINES
32              
33             =cut
34              
35             package Math::Symbolic::Custom::LaTeXDumper;
36              
37 2     2   417052 use 5.006;
  2         14  
  2         75  
38 2     2   10 use strict;
  2         3  
  2         62  
39 2     2   29 use warnings;
  2         3  
  2         69  
40 2     2   8 no warnings 'recursion';
  2         3  
  2         164  
41              
42             our $VERSION = '0.208';
43              
44 2     2   12 use Math::Symbolic::Custom::Base;
  2         3  
  2         81  
45 2     2   41 BEGIN { *import = \&Math::Symbolic::Custom::Base::aggregate_import }
46              
47 2     2   12 use Math::Symbolic::ExportConstants qw/:all/;
  2         4  
  2         1845  
48              
49 2     2   12 use Carp;
  2         10  
  2         4968  
50              
51             # Class Data: Special variable required by Math::Symbolic::Custom
52             # importing/exporting functionality.
53             # All subroutines that are to be exported to the Math::Symbolic::Custom
54             # namespace should be listed here.
55              
56             our $Aggregate_Export = [
57             qw/
58             to_latex
59             /
60             ];
61              
62             =head2 to_latex
63              
64             It returns a LaTeX representation of the Math::Symbolic tree
65             it is called on. The LaTeX is meant to be included in a LaTeX
66             source document. It does not include an enclosing math environment.
67              
68             The method uses named parameter passing style. Valid parameters are:
69              
70             =over 4
71              
72             =item implicit_multiplication
73              
74             Used to turn on the '.' (C<\cdot>) operators for all multiplications.
75             Defaults to true, that is, the multiplication operators are not present.
76              
77             =item no_fractions
78              
79             Use '/' division operator instead of fractions.
80             Defaults to false, that is, use fractions. See also:
81             C parameter.
82              
83             =item max_fractions
84              
85             Setting this parameter to a positive integer results in a limitation
86             of the number of nested fractions. It defaults to C<2>. Setting this to
87             C<0> results in arbitrarily nested fractions. To disable fractions
88             altogether, set the C parameter.
89              
90             =item exclude_signature
91              
92             By default, the method includes all variables' signatures in parenthesis
93             if present. Set this to true to omit variable signatures.
94              
95             =item replace_default_greek
96              
97             By default, all variable names are outputted as LaTeX in a way that
98             makes them show up exactly as they did in your code. If you set
99             this option to true, Math::Symbolic will try to replace as many
100             greek character names with the appropriates symbols as possible.
101              
102             Valid LaTeX symbols that are matched are:
103              
104             Lower case letters:
105             alpha, beta, gamma, delta, epsilon, zeta, eta, theta,
106             iota, kappa, lambda, mu, nu, xi, pi, rho, sigma,
107             tau, upsilon, phi, chi, psi, omega
108            
109             Variant forms of small letters:
110             varepsilon, vartheta, varpi, varrho, varsigma, varphi
111            
112             Upper case letters:
113             Gamma, Delta, Theta, Lambda, Xi, Pi, Sigma, Upsilon, Phi,
114             Psi, Omega
115              
116             =item variable_mappings
117              
118             Because not all variable names come out as you might want them to,
119             you may use the 'variable_mappings' option to replace variable names
120             in the output LaTeX stream with custom LaTeX. For example, the
121             variable x_i should probably indicate an 'x' with a subscripted i.
122             The argument to variable_mappings needs to be a hash reference which
123             contains variable name / LaTeX mapping pairs.
124              
125             If a variable is replaced in the above fashion, other options that
126             modify the outcome of the conversion of variable names to LaTeX are
127             ignored.
128              
129             =item subscript
130              
131             Set this option to a true value to have all underscores in variable names
132             interpreted as subscripts. Once an underscore is encountered, the rest of the
133             variable name is treated as subscripted. If multiple underscores are found, this
134             mechanism works recursively.
135              
136             Defaults to C. Set to a false value to turn this off. This is automatically
137             turned off for variables that are mapped to custom LaTeX by the C
138             parameter.
139              
140             =item no_sqrt
141              
142             By default, the dumper tries to convert exponents of 1/2 (or 0.5) or anything
143             numeric that ends up being 1/2 to a square root. If this gives inconvenient results,
144             set this option to a true value to turn the heuristics off.
145              
146             =back
147              
148             =cut
149              
150             sub to_latex {
151 2     2 1 9865 my $self = shift;
152 2         5 my %config = @_;
153 2 50       12 $config{implicit_multiplication} = 1
154             unless defined $config{implicit_multiplication};
155 2 50       9 $config{no_fractions} = 0 unless defined $config{no_fractions};
156 2 50       12 $config{max_fractions} = 2 if not exists $config{max_fractions};
157 2 50       11 $config{exclude_signature} = 0 unless defined $config{exclude_signature};
158 2 50       7 $config{no_sqrt} = 0 unless defined $config{no_sqrt};
159 2 50       7 $config{replace_default_greek} = 0
160             unless defined $config{replace_default_greek};
161              
162 2 50       10 $config{subscript} = 1 if not exists $config{subscript};
163              
164 2 50 33     22 $config{variable_mappings} = {}
165             if not exists $config{variable_mappings}
166             or not ref( $config{variable_mappings} ) eq 'HASH';
167              
168 2         11 my $default_greek = qr/(?
169             alpha|beta|gamma|delta|epsilon|zeta|eta|theta
170             |iota|kappa|lambda|mu|nu|xi|pi|rho|sigma|tau|upsilon
171             |phi|chi|psi|omega
172             |varepsilon|vartheta|varpi|varrho|varsigma|varphi
173             |Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Upsilon|Phi|Psi|Omega
174             (?![a-zA-Z])
175             /x;
176              
177             my $greekify = sub {
178 1     1   2 my $s = $_[0];
179 1 50       4 $s =~ s/($default_greek)/\\$1/g if $config{replace_default_greek};
180 1         3 return $s;
181 2         9 };
182            
183 2         4 my $subscriptify;
184             $subscriptify = sub {
185 1     1   2 my $s = $_[0];
186 1 50       3 $s = '' if not defined $s;
187 1 0       4 $s =~ s{_(.*)$}/length($1) == 1 ? "_$1" : '_{'.$subscriptify->($1).'}'/e if $config{subscript};
  0 50       0  
188 1         2 return $s;
189 2         8 };
190            
191 2         9 my $precedence = [
192             1, # B_SUM
193             1, # B_DIFFERENCE
194             5, # B_PRODUCT
195             5, # B_DIVISION
196             15, # U_MINUS
197             20, # U_P_DERIVATIVE
198             20, # U_T_DERIVATIVE
199             25, # B_EXP
200             50, # B_LOG
201             50, # U_SINE
202             50, # U_COSINE
203             50, # U_TANGENT
204             50, # U_COTANGENT
205             50, # U_ARCSINE
206             50, # U_ARCCOSINE
207             50, # U_ARCTANGENT
208             50, # U_ARCCOTANGENT
209             50, # U_SINE_H
210             50, # U_COSINE_H
211             50, # U_AREASINE_H
212             50, # U_AREACOSINE_H
213             50, # B_ARCTANGENT_TWO
214             ];
215              
216             my $op_to_tex = [
217              
218             # B_SUM
219 1     1   2 sub { "$_[0] + $_[1]" },
220              
221             # B_DIFFERENCE
222 0     0   0 sub { "$_[0] - $_[1]" },
223              
224             # B_PRODUCT
225             sub {
226 1 50   1   4 $config{implicit_multiplication}
227             ? "$_[0] $_[1]"
228             : "$_[0] \\cdot $_[1]";
229             },
230              
231             # B_DIVISION
232             sub {
233 0 0 0 0   0 if ( $config{no_fractions} ) {
    0          
234 0         0 "$_[0] / $_[1]"
235             }
236             elsif (!$config{max_fractions} or $config{max_fractions} > $_[2]) {
237 0         0 "\\frac{$_[0]}{$_[1]}"
238             }
239             else {
240 0         0 "$_[0] / $_[1]"
241             }
242             },
243              
244             # U_MINUS
245 1     1   2 sub { "-$_[0]" },
246              
247             # U_P_DERIVATIVE
248 0     0   0 sub { "\\frac{\\partial $_[0]}{\\partial $_[1]}" },
249              
250             # U_T_DERIVATIVE
251 0     0   0 sub { "\\frac{d $_[0]}{d $_[1]}" },
252              
253             # B_EXP
254             sub {
255 1 0 33 1   9 if (!$config{no_sqrt} and length($_[1]) > 2 and $_[1] !~ /\{|\}|\^|_|\-|\\|[A-DF-Za-df-z]/ and $_[1] - 0.5 < 1e-28) {
      33        
      33        
256 0         0 return "\\sqrt{$_[0]}";
257             }
258 1 50       6 length($_[1]) == 1 ? "$_[0]^$_[1]" : "$_[0]^{$_[1]}"
259             },
260              
261             # B_LOG
262 0     0   0 sub { "\\log_{$_[0]}$_[1]" },
263              
264             # U_SINE
265 0     0   0 sub { "\\sin{$_[0]}" },
266              
267             # U_COSINE
268 0     0   0 sub { "\\cos{$_[0]}" },
269              
270             # U_TANGENT
271 0     0   0 sub { "\\tan{$_[0]}" },
272              
273             # U_COTANGENT
274 0     0   0 sub { "\\cot{$_[0]}" },
275              
276             # U_ARCSINE
277 0     0   0 sub { "\\arcsin{$_[0]}" },
278              
279             # U_ARCCOSINE
280 0     0   0 sub { "\\arccos{$_[0]}" },
281              
282             # U_ARCTANGENT
283 0     0   0 sub { "\\arctan{$_[0]}" },
284              
285             # U_ARCCOTANGENT
286 0     0   0 sub { "\\mathrm{cosec}{$_[0]}" },
287              
288             # U_SINE_H
289 0     0   0 sub { "\\sinh{$_[0]}" },
290              
291             # U_COSINE_H
292 0     0   0 sub { "\\cosh{$_[0]}" },
293              
294             # U_AREASINE_H
295 0     0   0 sub { "\\mathrm{arsinh}{$_[0]}" },
296              
297             # U_AREACOSINE_H
298 0     0   0 sub { "\\mathrm{arcosh}{$_[0]}" },
299              
300             # B_ARCTANGENT_TWO
301 0     0   0 sub { "\\mathrm{atan2}{$_[0], $_[1]}" },
302 2         88 ];
303              
304             my $tex = $self->descend(
305             in_place => 0,
306 4     4   72 operand_finder => sub { $_[0]->descending_operands('all') },
307             before => sub {
308 7         27 $_[0]->{__precedences} = [
309             map {
310 9         29 my $ttype = $_->term_type();
311 7 100       29 if ( $ttype == T_OPERATOR ) {
    100          
    50          
312 3         10 $precedence->[ $_->type() ];
313             }
314             elsif ( $ttype == T_VARIABLE ) {
315 1         2 100;
316             }
317             elsif ( $ttype == T_CONSTANT ) {
318 3         8 100;
319             }
320 0         0 else { die "Should not be reached"; }
321 9     9   1132 } @{ $_[0]->{operands} }
322             ];
323 9   50     53 my $fraction_depth = $_[0]->{__fract_depth} || 0;
324 9 50 66     23 $fraction_depth++ if $_[0]->term_type() == T_OPERATOR and $_[0]->type() == B_DIVISION;
325 9         70 for (@{ $_[0]->{operands} }) {
  9         20  
326 7         15 $_->{__fract_depth} = $fraction_depth;
327             }
328 9         21 return ();
329             },
330             after => sub {
331 9     9   148 my $self = $_[0];
332 9         21 my $ttype = $self->term_type();
333 9 100       47 if ( $ttype == T_CONSTANT ) {
    100          
    50          
334 4         35 $self->{text} = $self->value();
335             }
336             elsif ( $ttype == T_VARIABLE ) {
337 1         5 my $name = $self->name();
338 1         6 my $edited_name = $name;
339 1 50       4 if ( exists $config{variable_mappings}{$name} ) {
340 0         0 $edited_name = $config{variable_mappings}{$name};
341             }
342             else {
343 1         3 $edited_name = $greekify->($name);
344 1         2 $edited_name = $subscriptify->($edited_name);
345             }
346 1 50       4 unless ( $config{exclude_signature} ) {
347             my @sig =
348             map {
349 1 0       5 if ( exists $config{variable_mappings}{$_} )
  0         0  
350             {
351 0         0 $config{variable_mappings}{$_};
352             }
353             else {
354 0         0 $_ = $subscriptify->($_);
355 0         0 s/_/\_/g;
356 0         0 $greekify->($_);
357             }
358             }
359             grep $_ ne $name, $self->signature();
360 1 50       23 if (@sig) {
361 0         0 $self->{text} = "$edited_name(" . join( ', ', @sig ) . ')';
362             }
363             else {
364 1         4 $self->{text} = $edited_name;
365             }
366             }
367             else {
368 0         0 $self->{text} = $edited_name;
369             }
370             }
371             elsif ( $ttype == T_OPERATOR ) {
372 4         11 my $type = $self->type();
373 4         19 my $prec = $precedence->[$type];
374 4         5 my $precs = $self->{__precedences};
375 4         4 my @ops = @{ $self->{operands} };
  4         6  
376 4 50 33     11 unless ( $type == B_DIVISION and !$config{no_fractions} ) {
377 4         9 for ( my $i = 0 ; $i < @ops ; $i++ ) {
378 7         28 my $obj = $ops[$i];
379 7 100 33     90 if ( $precs->[$i] < $prec
      33        
      66        
      66        
      33        
      66        
      100        
      33        
      33        
380             or $precs->[$i] == $prec && $prec == 50 # prec == 50 is a function call
381             or $i == 1 # second operand
382             && ($type == B_PRODUCT||$type == B_SUM||$type==B_DIFFERENCE)
383             && ($obj->term_type == T_CONSTANT && $obj->value < 0)
384             || ($obj->term_type == T_OPERATOR && $obj->type == U_MINUS) )
385             {
386 1         17 $ops[$i]->{text} = '\\left(' . $ops[$i]->{text} . '\\right)'
387             }
388             }
389             }
390 4   50     39 my $fraction_depth = $self->{__fract_depth}||0;
391 4         16 my $text = $op_to_tex->[$type]->((map $_->{text}, @ops), $fraction_depth);
392 4         13 $self->{text} = $text;
393             }
394             else {
395 0         0 die "Should never be reached";
396             }
397             },
398 2         43 );
399              
400 2         150 return $tex->{text};
401             }
402              
403             1;
404             __END__