The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.

=encoding utf8

=head1 NAME

Math::Symbolic::Custom::LaTeXDumper - Math::Symbolic LaTeX output

=head1 SYNOPSIS

  use Math::Symbolic qw/parse_from_string/;
  use Math::Symbolic::Custom::LaTeXDumper;
  $term = parse_from_string(...);
  print $term->to_latex(...);

=head1 DESCRIPTION

This class provides the C<to_latex()> method for all L<Math::Symbolic>
trees. It is a rewrite of the C<to_latex()> method that was supplied by
C<Math::Symbolic> prior to version 0.201.

For details on how the custom method delegation model works, please have
a look at the C<Math::Symbolic::Custom> and C<Math::Symbolic::Custom::Base>
classes.

=head2 EXPORT

Please see the docs for C<Math::Symbolic::Custom::Base> for details, but
you should not try to use the standard Exporter semantics with this
class.

=head1 SUBROUTINES

=cut

package Math::Symbolic::Custom::LaTeXDumper;

use 5.006;
use strict;
use warnings;
no warnings 'recursion';

our $VERSION = '0.208';

use Math::Symbolic::Custom::Base;
BEGIN { *import = \&Math::Symbolic::Custom::Base::aggregate_import }

use Math::Symbolic::ExportConstants qw/:all/;

use Carp;

# Class Data: Special variable required by Math::Symbolic::Custom
# importing/exporting functionality.
# All subroutines that are to be exported to the Math::Symbolic::Custom
# namespace should be listed here.

our $Aggregate_Export = [
    qw/
      to_latex
      /
];

=head2 to_latex

It returns a LaTeX representation of the Math::Symbolic tree
it is called on. The LaTeX is meant to be included in a LaTeX
source document. It does not include an enclosing math environment.

The method uses named parameter passing style. Valid parameters are:

=over 4

=item implicit_multiplication

Used to turn on the '.' (C<\cdot>) operators for all multiplications.
Defaults to true, that is, the multiplication operators are not present.

=item no_fractions

Use '/' division operator instead of fractions.
Defaults to false, that is, use fractions. See also:
C<max_fractions> parameter.

=item max_fractions

Setting this parameter to a positive integer results in a limitation
of the number of nested fractions. It defaults to C<2>. Setting this to
C<0> results in arbitrarily nested fractions. To disable fractions
altogether, set the C<no_fraction> parameter.

=item exclude_signature

By default, the method includes all variables' signatures in parenthesis
if present. Set this to true to omit variable signatures.

=item replace_default_greek

By default, all variable names are outputted as LaTeX in a way that
makes them show up exactly as they did in your code. If you set
this option to true, Math::Symbolic will try to replace as many
greek character names with the appropriates symbols as possible.

Valid LaTeX symbols that are matched are:

  Lower case letters:
    alpha, beta, gamma, delta, epsilon, zeta, eta, theta,
    iota, kappa, lambda, mu, nu, xi, pi, rho, sigma,
    tau, upsilon, phi, chi, psi, omega
  
  Variant forms of small letters:
    varepsilon, vartheta, varpi, varrho, varsigma, varphi
  
  Upper case letters:
    Gamma, Delta, Theta, Lambda, Xi, Pi, Sigma, Upsilon, Phi,
    Psi, Omega

=item variable_mappings

Because not all variable names come out as you might want them to,
you may use the 'variable_mappings' option to replace variable names
in the output LaTeX stream with custom LaTeX. For example, the
variable x_i should probably indicate an 'x' with a subscripted i.
The argument to variable_mappings needs to be a hash reference which
contains variable name / LaTeX mapping pairs.

If a variable is replaced in the above fashion, other options that
modify the outcome of the conversion of variable names to LaTeX are
ignored.

=item subscript

Set this option to a true value to have all underscores in variable names
interpreted as subscripts. Once an underscore is encountered, the rest of the
variable name is treated as subscripted. If multiple underscores are found, this
mechanism works recursively.

Defaults to C<true>. Set to a false value to turn this off. This is automatically
turned off for variables that are mapped to custom LaTeX by the C<variable_mappings>
parameter.

=item no_sqrt

By default, the dumper tries to convert exponents of 1/2 (or 0.5) or anything
numeric that ends up being 1/2 to a square root. If this gives inconvenient results,
set this option to a true value to turn the heuristics off.

=back

=cut

sub to_latex {
    my $self   = shift;
    my %config = @_;
    $config{implicit_multiplication} = 1
      unless defined $config{implicit_multiplication};
    $config{no_fractions}      = 0 unless defined $config{no_fractions};
    $config{max_fractions}     = 2 if not exists $config{max_fractions};
    $config{exclude_signature} = 0 unless defined $config{exclude_signature};
    $config{no_sqrt} = 0 unless defined $config{no_sqrt};
    $config{replace_default_greek} = 0
      unless defined $config{replace_default_greek};

    $config{subscript} = 1 if not exists $config{subscript};

    $config{variable_mappings} = {}
      if not exists $config{variable_mappings}
      or not ref( $config{variable_mappings} ) eq 'HASH';

    my $default_greek = qr/(?<![a-zA-Z])
        alpha|beta|gamma|delta|epsilon|zeta|eta|theta
        |iota|kappa|lambda|mu|nu|xi|pi|rho|sigma|tau|upsilon
        |phi|chi|psi|omega
        |varepsilon|vartheta|varpi|varrho|varsigma|varphi
        |Gamma|Delta|Theta|Lambda|Xi|Pi|Sigma|Upsilon|Phi|Psi|Omega
        (?![a-zA-Z])
    /x;

    my $greekify = sub {
        my $s = $_[0];
        $s =~ s/($default_greek)/\\$1/g if $config{replace_default_greek};
        return $s;
    };
    
    my $subscriptify;
       $subscriptify = sub {
        my $s = $_[0];
        $s = '' if not defined $s;
        $s =~ s{_(.*)$}/length($1) == 1 ? "_$1" : '_{'.$subscriptify->($1).'}'/e if $config{subscript};
        return $s;
    };
    
    my $precedence = [
        1,     # B_SUM
        1,     # B_DIFFERENCE
        5,     # B_PRODUCT
        5,     # B_DIVISION
        15,    # U_MINUS
        20,    # U_P_DERIVATIVE
        20,    # U_T_DERIVATIVE
        25,    # B_EXP
        50,    # B_LOG
        50,    # U_SINE
        50,    # U_COSINE
        50,    # U_TANGENT
        50,    # U_COTANGENT
        50,    # U_ARCSINE
        50,    # U_ARCCOSINE
        50,    # U_ARCTANGENT
        50,    # U_ARCCOTANGENT
        50,    # U_SINE_H
        50,    # U_COSINE_H
        50,    # U_AREASINE_H
        50,    # U_AREACOSINE_H
        50,    # B_ARCTANGENT_TWO
    ];

    my $op_to_tex = [

        # B_SUM
        sub { "$_[0] + $_[1]" },

        # B_DIFFERENCE
        sub { "$_[0] - $_[1]" },

        # B_PRODUCT
        sub {
            $config{implicit_multiplication}
              ? "$_[0] $_[1]"
              : "$_[0] \\cdot $_[1]";
        },

        # B_DIVISION
        sub {
            if ( $config{no_fractions} ) {
                "$_[0] / $_[1]"
            }
            elsif (!$config{max_fractions} or $config{max_fractions} > $_[2]) {
                "\\frac{$_[0]}{$_[1]}"
            }
            else {
                "$_[0] / $_[1]"
            }
        },

        # U_MINUS
        sub { "-$_[0]" },

        # U_P_DERIVATIVE
        sub { "\\frac{\\partial $_[0]}{\\partial $_[1]}" },

        # U_T_DERIVATIVE
        sub { "\\frac{d $_[0]}{d $_[1]}" },

        # B_EXP
        sub {
            if (!$config{no_sqrt} and length($_[1]) > 2 and $_[1] !~ /\{|\}|\^|_|\-|\\|[A-DF-Za-df-z]/ and $_[1] - 0.5 < 1e-28) {
                return "\\sqrt{$_[0]}";
            }
            length($_[1]) == 1 ? "$_[0]^$_[1]" : "$_[0]^{$_[1]}"
        },

        # B_LOG
        sub { "\\log_{$_[0]}$_[1]" },

        # U_SINE
        sub { "\\sin{$_[0]}" },

        # U_COSINE
        sub { "\\cos{$_[0]}" },

        # U_TANGENT
        sub { "\\tan{$_[0]}" },

        # U_COTANGENT
        sub { "\\cot{$_[0]}" },

        # U_ARCSINE
        sub { "\\arcsin{$_[0]}" },

        # U_ARCCOSINE
        sub { "\\arccos{$_[0]}" },

        # U_ARCTANGENT
        sub { "\\arctan{$_[0]}" },

        # U_ARCCOTANGENT
        sub { "\\mathrm{cosec}{$_[0]}" },

        # U_SINE_H
        sub { "\\sinh{$_[0]}" },

        # U_COSINE_H
        sub { "\\cosh{$_[0]}" },

        # U_AREASINE_H
        sub { "\\mathrm{arsinh}{$_[0]}" },

        # U_AREACOSINE_H
        sub { "\\mathrm{arcosh}{$_[0]}" },

        # B_ARCTANGENT_TWO
        sub { "\\mathrm{atan2}{$_[0], $_[1]}" },
    ];

    my $tex = $self->descend(
        in_place       => 0,
        operand_finder => sub { $_[0]->descending_operands('all') },
        before         => sub {
            $_[0]->{__precedences} = [
                map {
                    my $ttype = $_->term_type();
                    if ( $ttype == T_OPERATOR ) {
                        $precedence->[ $_->type() ];
                    }
                    elsif ( $ttype == T_VARIABLE ) {
                        100;
                    }
                    elsif ( $ttype == T_CONSTANT ) {
                        100;
                    }
                    else { die "Should not be reached"; }
                  } @{ $_[0]->{operands} }
            ];
            my $fraction_depth = $_[0]->{__fract_depth} || 0;
            $fraction_depth++ if $_[0]->term_type() == T_OPERATOR and $_[0]->type() == B_DIVISION;
            for (@{ $_[0]->{operands} }) {
                $_->{__fract_depth} = $fraction_depth;
            }
            return ();
        },
        after => sub {
            my $self  = $_[0];
            my $ttype = $self->term_type();
            if ( $ttype == T_CONSTANT ) {
                $self->{text} = $self->value();
            }
            elsif ( $ttype == T_VARIABLE ) {
                my $name        = $self->name();
                my $edited_name = $name;
                if ( exists $config{variable_mappings}{$name} ) {
                    $edited_name = $config{variable_mappings}{$name};
                }
                else {
                    $edited_name = $greekify->($name);
                    $edited_name = $subscriptify->($edited_name);
                }
                unless ( $config{exclude_signature} ) {
                    my @sig =
                      map {
                        if ( exists $config{variable_mappings}{$_} )
                        {
                            $config{variable_mappings}{$_};
                        }
                        else {
                            $_ = $subscriptify->($_);
                            s/_/\_/g;
                            $greekify->($_);
                        }
                      }
                      grep $_ ne $name, $self->signature();
                    if (@sig) {
                        $self->{text} = "$edited_name(" . join( ', ', @sig ) . ')';
                    }
                    else {
                        $self->{text} = $edited_name;
                    }
                }
                else {
                    $self->{text} = $edited_name;
                }
            }
            elsif ( $ttype == T_OPERATOR ) {
                my $type  = $self->type();
                my $prec  = $precedence->[$type];
                my $precs = $self->{__precedences};
                my @ops   = @{ $self->{operands} };
                unless ( $type == B_DIVISION and !$config{no_fractions} ) {
                    for ( my $i = 0 ; $i < @ops ; $i++ ) {
                        my $obj = $ops[$i];
                        if ( $precs->[$i] < $prec
                             or $precs->[$i] == $prec && $prec == 50 # prec == 50 is a function call
                             or $i == 1 # second operand
                                && ($type == B_PRODUCT||$type == B_SUM||$type==B_DIFFERENCE)
                                && ($obj->term_type == T_CONSTANT && $obj->value < 0)
                                   || ($obj->term_type == T_OPERATOR && $obj->type == U_MINUS) )
                        {
                            $ops[$i]->{text} = '\\left(' . $ops[$i]->{text} . '\\right)'
                        }
                    }
                }
                my $fraction_depth = $self->{__fract_depth}||0;
                my $text = $op_to_tex->[$type]->((map $_->{text}, @ops), $fraction_depth);
                $self->{text} = $text;
            }
            else {
                die "Should never be reached";
            }
        },
    );

    return $tex->{text};
}

1;
__END__

=head1 SEE ALSO

New versions of this module can be found on
http://steffen-mueller.net or CPAN. The module development takes place on
Sourceforge at http://sourceforge.net/projects/math-symbolic/

L<Math::Symbolic::Custom>
L<Math::Symbolic::Custom::DefaultMods>
L<Math::Symbolic::Custom::DefaultDumpers>
L<Math::Symbolic::Custom::DefaultTests>
L<Math::Symbolic>

=head1 AUTHOR

Steffen Müller, C<smueller@cpan.org>

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2006, 2008, 2011, 2013 by Steffen Mueller

This library is free software; you can redistribute it and/or modify
it under the same terms as Perl itself, either Perl version 5.8.0 or,
at your option, any later version of Perl 5 you may have available.

=cut