5

I had an idea about how to answer this question and started putting some things together. I wanted to put something together that was fairly powerful and could handle simplification of linear expressions.

I should start by apologizing for the length of this example. It is quite long. But all my attempts at reducing it have either resulted in code that compiles, or results in code that has other issues which, when resolved seem to shed no light on what's happening here. I can say that the first portion of code I post below works reasonably well (it's not perfect, but it does get close to the job I want).

Regarding simplifying linear expressions, I got something that worked pretty well. Here's the code I put together to do that; I called the file containing this code reduce_terms.tex:

\ExplSyntaxOn
%-@-(1)------------------------------------------------------------------------------------
%% some SEVEN commands to help avoid errors in naming the "counters"
\cs_new:Npn \_stirling_var_get:n    #1   { \int_use:c  { l/stirling/var/ #1 /int } }
\cs_new:Npn \_stirling_var_create:n #1   { \int_new:c  { l/stirling/var/ #1 /int } }
\cs_new:Npn \_stirling_var_incr:nn  #1#2 { \int_add:cn { l/stirling/var/ #1 /int } {#2}}
\cs_new:Npn \_stirling_var_decr:nn  #1#2 { \int_sub:cn { l/stirling/var/ #1 /int } {#2}}
\cs_new:Npn \_stirling_var_unset:n  #1   
  { 
    \cs_undefine:c { l/stirling/var/ #1 /int } 
  }
\prg_new_conditional:Npnn \_stirling_var_if_exists:n #1 { T , F , TF }
  { 
    \int_if_exist:cTF { l/stirling/var/ #1 /int } 
      { \prg_return_true: } 
      { \prg_return_false: } 
  }
%%--------------------------------------------------------------------------------
\cs_new:Npn \_stirling_var_advance_counter_according_to_term_sign:nn #1#2
  {
    \bool_if:NTF \l_term_is_positive_bool
      { \_stirling_var_incr:nn { #1 }{ #2 } }
      { \_stirling_var_decr:nn { #1 }{ #2 } }
  }
%%--------------------------------------------------------------------------------
%% a crude attempt to determine whether a token is a digit
\prg_new_conditional:Npnn \is_digit:n #1 { p, T, F , TF }
  { \int_compare:nTF { `0 > `#1 }
                     { \prg_return_false: }
                     { \int_compare:nTF { `#1 > `9 }
                                        { \prg_return_false: }
                                        { \prg_return_true:  }
      }}

%-@-(2)------------------------------------------------------------------------------------
%%--------------------------------------------------------------------------------
\seq_new:N \l_stirling_ordered_list_of_all_variable_names_seq
\seq_new:N \l_stirling_list_of_plus_separated_tokens_seq
\seq_new:N \l_stirling_list_of_minus_separated_tokens_seq
%% boolean to track whether term was preceded by a "+" or "-"
\bool_new:N \l_term_is_positive_bool
%% token list to store the first term from a string of "-" separated items.
\tl_new:N  \l_stirling_lead_tl

%-@-(3)------------------------------------------------------------------------------------
\bool_new:N \l_stirling_have_not_hit_nondigit_token_yet_bool
\tl_new:N   \l_stirling_tmp_a
\tl_new:N   \l_stirling_tmp_b
\tl_new:N   \l_stirling_tmp_c

\cs_new:Npn \_stirling_var_read_coefficient:n #1 
  {
    \bool_set_true:N \l_stirling_have_not_hit_nondigit_token_yet_bool
    \tl_clear:N      \l_stirling_tmp_a
    \tl_clear:N      \l_stirling_tmp_b
    \tl_clear:N      \l_stirling_tmp_c
    \tl_set:Nf       \l_stirling_tmp_a { \tl_trim_spaces:n { #1 }}
    %% if expression starts with a negative sign, you don't want to mistake
    %% there will be an empty token passed.  You want to ignore this term 
    %% otherwise the constant term will be augmented.
    \tl_if_empty:NF \l_stirling_tmp_a
    {
      \tl_map_inline:Nn \l_stirling_tmp_a
        {
          %% collect all the digits to build the numerical coefficient.
          %% As soon as we hit something that is not a digit, stop and
          %% treat all remaining tokens as part of the variable name 
          %% regardless of whether later digits will occur.
          \bool_if:NTF \l_stirling_have_not_hit_nondigit_token_yet_bool
            {
              \is_digit:nTF { ##1 }
                {  \tl_put_right:Nn  \l_stirling_tmp_b { ##1 } }
                {  \bool_set_false:N \l_stirling_have_not_hit_nondigit_token_yet_bool
                   \tl_put_right:Nn  \l_stirling_tmp_c { ##1 } }
            } 
            {
              \tl_put_right:Nn \l_stirling_tmp_c { ##1 }
            } 
        }
      %% The contents of \l_stirling_tmp_b are to be used as a number.  If
      %% no digits were found, then this variable is empty.  If it is empty,
      %% then it should be set to the value of "1".
      \tl_if_empty:NT \l_stirling_tmp_b { \tl_set:Nn \l_stirling_tmp_b { 1 } }
      \_stirling_var_if_exists:nTF { \l_stirling_tmp_c } 
        { 
          \_stirling_var_advance_counter_according_to_term_sign:nn { \l_stirling_tmp_c } { \l_stirling_tmp_b}
        }
        {
          \_stirling_var_create:n { \l_stirling_tmp_c }
          \_stirling_var_advance_counter_according_to_term_sign:nn { \l_stirling_tmp_c } { \l_stirling_tmp_b}
          %% "x" may be stronger than you need.  But you do need to expand so that it's
          %% not the token \l_stirling_tmp_c sitting in the sequence.
          \seq_put_right:Nx \l_stirling_ordered_list_of_all_variable_names_seq { \l_stirling_tmp_c }
        }
    }
  }     

%-@-(4)------------------------------------------------------------------------------------
\cs_new:Npn \_parse_expression:n #1
  { %%\fbox{\ttfamily#1} 
    \__parse_addition:n {#1}
  }

\cs_new:Npn \__parse_addition:n #1
  {
    \seq_set_split:Nnn \l_stirling_list_of_plus_separated_tokens_seq { + } { #1 }
    \seq_map_function:NN \l_stirling_list_of_plus_separated_tokens_seq 
                         \__parse_subtraction:n
  }

\cs_new:Npn \__parse_subtraction:n #1
  {
      \seq_set_split:Nnn \l_stirling_list_of_minus_separated_tokens_seq { - } { #1 }
      \seq_pop_left:NN   \l_stirling_list_of_minus_separated_tokens_seq \l_stirling_lead_tl
      \bool_set_true:N   \l_term_is_positive_bool
      \_stirling_var_read_coefficient:n { \l_stirling_lead_tl }
      \bool_set_false:N  \l_term_is_positive_bool
      \seq_map_function:NN \l_stirling_list_of_minus_separated_tokens_seq
                           \_stirling_var_read_coefficient:n
  }

%-@-(5)------------------------------------------------------------------------------------
\cs_new:Npn \_reset_coefficient_counters:n #1
  {
    \seq_map_function:NN \l_stirling_ordered_list_of_all_variable_names_seq
                         \_stirling_var_unset:n 
  }

%-@-(6)------------------------------------------------------------------------------------
%%--------------------------------------------------------------------------------
\cs_new:Npn \_write_expression:n #1
  {
    \seq_map_function:NN \l_stirling_ordered_list_of_all_variable_names_seq
                         \_stirling_var_set_according_to_coefficient:n 
  }

\tl_new:N        \l_stirling_plus_sign_tl
\bool_new:N      \l_stirling_passed_first_term_bool
\bool_set_true:N \l_stirling_passed_first_term_bool

\cs_generate_variant:Nn \tl_put_right:Nn {Nf}
\cs_new:Npn \_stirling_var_set_according_to_coefficient:n #1
  {
    %% No "+" should precent the lead term
    \bool_if:NTF   \l_stirling_passed_first_term_bool
      { \tl_set:Nn \l_stirling_plus_sign_tl {   } }
      { \tl_set:Nn \l_stirling_plus_sign_tl { + } }
    \int_compare:nNnT { \_stirling_var_get:n { #1 } } > { 0 } { \l_stirling_plus_sign_tl }
    \int_case:nnn { \_stirling_var_get:n { #1 } }
      { {  0 } {     }
        {  1 } { \tl_if_empty:nTF {#1}{ 1 }{#1}  \bool_set_false:N \l_stirling_passed_first_term_bool}
        { -1 } { -#1                             \bool_set_false:N \l_stirling_passed_first_term_bool}
      } { \_stirling_var_get:n { #1 } #1         \bool_set_false:N \l_stirling_passed_first_term_bool}
    %% as soon as you've processed first coefficient, change
    %% the boolean that tracks this.  Otherwise, you may not
    %% have "+" signs where you believe they should be.  Also,
    %% without this boolean, you might have "+" where you'd prefer
    %% not to have them:  such as in front of the first term.
  }

\ExplSyntaxOff

After getting this much up and running, I thought it would be a simple task to create the partial derivatives. But I got stuck with the denominators. Here's the code that I wrote trying to get the denominator formatted correctly. I've embedded several of my approaches to get this right--some are clearly wrong--but nothing I do seems to work. I'm quite mystified.

The idea here is that I format each part of the denominator and then store the formatting in a sequence variable which will be called later on when I actually want to print out the derivative.

I've already tried posting a question early regarding this next portion of code. I was hoping I'd narrowed it down to something that would prove fruit. But alas, even if I managed to narrow it down correctly, I'm having difficulty getting the posted solutions to cooperate.

This following code I saved in a file I called find_derivatives.tex:

\ExplSyntaxOn
\seq_new:N  \l_stirling_derivative_denominator_raw_seq
\seq_new:N \l_stirling_denominator_pow_seq

\tl_new:N \l_stirling_tmp_d
\cs_generate_variant:Nn \tl_set:Nn {Nx}
\cs_generate_variant:Nn \seq_gput_right:Nn { Nf}
\cs_new:Npn \_parse_derivative:nn #1#2 
  {
    \_parse_expression:n {#2}
    %%---------------------------------------------------------------------------
    %% This next line gets it completely wrong                                   
    \seq_gput_right:Nn \l_stirling_denominator_pow_seq { d#1^{ \_write_expression:n {}  }}
    %%---------------------------------------------------------------------------
    %% Uncommenting this next set of lines will also result in an error.         
    %% This next approach gives me an "inaccessible" error.                      
    %%<2>%% \tl_set:Nx \l_stirling_tmp_d { \_write_expression:n {}}              
    %%<2>%% \seq_gput_right:Nx \l_stirling_denominator_pow_seq { d#1^{ \l_stirling_tmp_d  }}
    %%---------------------------------------------------------------------------
    %% Uncommenting this line, things are a bit better.  But this seems comletely
    %% contrary to what I want to do.                                            
    %%<3>%% \seq_gput_right:Nn \l_stirling_denominator_pow_seq { d#1^{ \_parse_expression:n {#2} \_write_expression:n {}  }}
    %%---------------------------------------------------------------------------
    %% This line shouldn't be here, but it shows the values that should          
    %% be appearing in the exponents, but which aren't.                          
    \fbox{\_write_expression:n{}}\,
    \_reset_coefficient_counters:n {}  
  }

\cs_new:Npn \parse_derivative:nn #1 #2 
  {
    \seq_set_split:Nnn \l_stirling_derivative_denominator_raw_seq { , } { #2 }
    \seq_map_inline:Nn \l_stirling_derivative_denominator_raw_seq
      {
        \group_begin:
        \tl_if_single:nTF { ##1 }
          { \_parse_derivative:nn { ##1 } { 1 } } 
          { \_parse_derivative:nn ##1 } 
        \group_end:
      } 

    \frac{#1}{ \seq_use:Nn \l_stirling_denominator_pow_seq {\,} }
  }    

\NewDocumentCommand{\derivative}{ mm }
    { \parse_derivative:nn {#1}{#2} }

\ExplSyntaxOff

Here's the master document:

\documentclass[12pt]{article}
\usepackage{amsmath,amssymb}
\usepackage{xparse}
\input{lib/reduce_terms}
\input{lib/find_derivatives}
\ExplSyntaxOn
%% Not quite overkill.
\cs_new:Npn \simplify_expression:n #1
  {
    \group_begin:
    \_parse_expression:n {#1}
    \_write_expression:n {}
    \_reset_coefficient_counters:n {}
    \group_end:
  }
\NewDocumentCommand{\simplifyExpression}{ m }
    { \simplify_expression:n {#1} }
\ExplSyntaxOff
\pagestyle{empty}
\begin{document}

\begin{align*}
   \simplifyExpression{12-x-3y-k-x-3-y-y-w+4w+10x-w2-7+5} \\
   \simplifyExpression{3a+x-2y-4x}                        \\
   \simplifyExpression{-3+7-x+2y}                         \\
   \simplifyExpression{-3}                                \\
   \simplifyExpression{1+x}                               \\
   \simplifyExpression{1}                                 \\
   \simplifyExpression{x}                                 \\
   \simplifyExpression{x+y-x}                             \\
\end{align*}

\[
  \derivative{x}{{v}{3},{w}{3k+2-k},y,{z}{m+k}}
\]

\end{document}

which results in

enter image description here

My question is about how to get the variables in the denominator to have the correct exponents. The expressions in the fboxs are what should be appearing as the exponents.

1

I know this is a ridiculously old thread to resurrect, and you've probably moved on or solved the problem another way, but I've been working through expansion in expl3 myself and found this to be an intriguing question.

The core problem is that \_stirling_var_set_according_to_coefficient:n is not fully expandable, even though you've declared it with \cs_new:Npn, and so you get the issues you've encountered when you put \write_expression:n, which calls it, in an x argument. Indeed, most of your functions use non-expandable functions somewhere inside them, which means you're potentially setting yourself up for trouble in other places as well.

The basic solution is to put the fully formatted sequence of coefficients and variables into a tokenlist once the parsing is complete and print that rather than trying to assemble it on the fly.

The version below is a fairly significant rewrite. I fixed some other bugs and regularized the nomenclature a bit. I also replaced your coefficient-detection algorithm with a regexp-based solution, which cut down on the length of the code significantly.

\documentclass[12pt]{article}
\usepackage{amsmath,amssymb}
\usepackage{xparse}

\ExplSyntaxOn

\tl_new:N \g_stirling_formatted_expression_tl
\tl_new:N \l_stirling_var_tl
\tl_new:N \l_stirling_coef_tl
\tl_new:N \l_stirling_plus_tl
\tl_const:Nn \c_stirling_minus_tl {-}
\tl_const:Nn \c_stirling_one_tl {1}

\seq_new:N \l_stirling_denominator_raw_seq
\seq_new:N \g_stirling_denominator_pow_seq
\seq_new:N \l_stirling_variable_names_seq
\seq_new:N \l_stirling_coef_and_var_seq

\regex_const:Nn \c_stirling_find_coef_regex { (?|\s*+?\s*)(-?[0-9]*)\s*([^\s+-]*) }

\cs_new:Nn \_stirling_var_get:n { \int_use:c { l/stirling/var/ #1 /int } }
\cs_new_protected:Nn \_stirling_var_create:n { \int_new:c { l/stirling/var/ #1 /int } }
\cs_new_protected:Nn \_stirling_var_unset:n { \cs_undefine:c { l/stirling/var/ #1 /int } }
\cs_new_protected:Nn \_stirling_var_add:nn { \int_add:cn { l/stirling/var/ #1 /int } {#2} }

\prg_new_conditional:Nnn \_stirling_var_if_exists:n {T,F,TF}
{ 
  \int_if_exist:cTF { l/stirling/var/ #1 /int } 
  { \prg_return_true: } 
  { \prg_return_false: } 
}

\cs_new_protected:Nn \_stirling_reset_counters:
{
  \seq_map_function:NN \l_stirling_variable_names_seq
    \_stirling_var_unset:n 
}

\cs_new_protected:Nn \_stirling_format_expression:
{
  \tl_gclear:N \g_stirling_formatted_expression_tl
  \seq_map_function:NN \l_stirling_variable_names_seq
    \_stirling_format_var:n
}

\cs_new_protected:Nn \_stirling_format_var:n
{
  % No "+" should precede the lead term
  \tl_if_empty:NTF \g_stirling_formatted_expression_tl
  {
    \tl_clear:N \l_stirling_plus_tl
  }
  {
    \tl_set:Nn \l_stirling_plus_tl {+}
  }
  \tl_gput_right:Nx \g_stirling_formatted_expression_tl
  {
    \int_case:nnF { \_stirling_var_get:n { #1 } }
    { {  0 } {}
      {  1 } {\l_stirling_plus_tl \tl_if_empty:nTF {#1}{ 1 }{#1} }
      { -1 } {\tl_if_empty:nTF {#1}{ -1 }{-#1} }
    }
    {
      \int_compare:nNnT {\_stirling_var_get:n {#1}} > {0}
        {\l_stirling_plus_tl}
      \_stirling_var_get:n {#1} #1
    }
  }
}

% the regex split creates triplets in the sequence. The first is always blank,
% the second is the coefficient, and the third is the variable.
\cs_new_protected:Nn \_parse_expression:n
{
  \regex_split:NnN \c_stirling_find_coef_regex {#1} \l_stirling_coef_and_var_seq
  % pop triplets from the sequence until we're done
  \bool_until_do:nn {\seq_if_empty_p:N \l_stirling_coef_and_var_seq}
  {
    % the first pop discards an empty item
    \seq_pop_left:NN \l_stirling_coef_and_var_seq \l_stirling_var_tl
    % get the coefficient
    \seq_pop_left:NN \l_stirling_coef_and_var_seq \l_stirling_coef_tl
    % get the variable
    \seq_pop_left:NN \l_stirling_coef_and_var_seq \l_stirling_var_tl
    % create a variable to track cumulative coefficients
    \_stirling_var_if_exists:nF {\l_stirling_var_tl}
    {
      \_stirling_var_create:n {\l_stirling_var_tl}
      \seq_put_right:Nx \l_stirling_variable_names_seq { \l_stirling_var_tl }
    }
    % normalize 1 and -1
    \tl_if_empty:NTF \l_stirling_coef_tl
    {
      \tl_set:Nn \l_stirling_coef_tl {1}
    }
    {
      \tl_if_eq:NNT \l_stirling_coef_tl \c_stirling_minus_tl
      {
        \tl_set:Nn \l_stirling_coef_tl {-1}
      }
    }
    % add coefficient to existing coefficient of same variable
    \_stirling_var_add:nn {\l_stirling_var_tl} {\l_stirling_coef_tl}
  }
  % convert the formatted sequence into a tokenlist
  \_stirling_format_expression:
}

\cs_new_protected:Nn \_parse_derivative:nn
{
  \_parse_expression:n {#2}
  % Skip exponent if it's just 1
  \tl_if_eq:NNTF \g_stirling_formatted_expression_tl \c_stirling_one_tl
  {
    \seq_gput_right:Nx \g_stirling_denominator_pow_seq {d#1}
  }
  {
    \seq_gput_right:Nx \g_stirling_denominator_pow_seq
      {d#1^{ \g_stirling_formatted_expression_tl } }
  }
  \_stirling_reset_counters:
}

\cs_new_protected:Nn \parse_derivative:n
{
  \seq_set_split:Nnn \l_stirling_denominator_raw_seq { , } { #1 }
  \seq_map_inline:Nn \l_stirling_denominator_raw_seq
  {
    \group_begin:
    \tl_if_single:nTF { ##1 }
    { \_parse_derivative:nn { ##1 } { 1 } } 
    { \_parse_derivative:nn ##1 } 
    \group_end:
  } 
}    

\cs_new_protected:Nn \derivative:nn
{
  \parse_derivative:n {#2}
  \frac{#1}{ \seq_use:Nn \g_stirling_denominator_pow_seq {\,} }  
}

\cs_new_protected:Nn \simplify_expression:n
{
  \group_begin:
  \_parse_expression:n {#1}
  \g_stirling_formatted_expression_tl
  \_stirling_reset_counters:
  \group_end:
}

\NewDocumentCommand{\derivative}{ m m }
{
  \derivative:nn {#1}{#2}
}

\NewDocumentCommand{\simplifyExpression}{ m }
{
  \simplify_expression:n {#1}
}

\ExplSyntaxOff

\begin{document}

  \begin{align*}
  \simplifyExpression{12-x-3y-k-x-3-y-y-w+4w+10x-w2-7+5} \\
  \simplifyExpression{3a+x-2y-4x}                        \\
  \simplifyExpression{-3+7-x+2y}                         \\
  \simplifyExpression{-3}                                \\
  \simplifyExpression{1+x}                               \\
  \simplifyExpression{1}                                 \\
  \simplifyExpression{x}                                 \\
  \simplifyExpression{x+y-x}                             \\
  \end{align*}

  \[
  \derivative{x}{{v}{3},{w}{3k+2-k},y,{z}{m+k}}
  \]

\end{document}

Here's the result:

enter image description here

|improve this answer|||||

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.