%% broydensolve.sty
%% Copyright 2025 Matthias Floré
%
% 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
%   http://www.latex-project.org/lppl.txt
% and version 1.3c or later is part of all distributions of LaTeX
% version 2005/12/01 or later.
%
% This work has the LPPL maintenance status `maintained'.
% 
% The Current Maintainer of this work is Matthias Floré.
%
% This work consists of the files broydensolve-doc.pdf, broydensolve.sty,
% broydensolve-doc.tex and README.md.
\NeedsTeXFormat{LaTeX2e}
\ProvidesExplPackage{broydensolve}{2025/07/24}{2.0}{Solve a system of equations with Broyden's good method}

%%> \subsection{Variables}

\bool_new:N \l__broydensolve_do_bool

\clist_new:N \g__broydensolve_roots_clist

\fp_new:N \l__broydensolve_aux_fp
\fp_new:N \g__broydensolve_coord_x_fp
\fp_new:N \g__broydensolve_coord_y_fp
\fp_new:N \l__broydensolve_det_fp
\fp_new:N \l__broydensolve_norm_h_fp
\fp_new:N \l__broydensolve_norm_x_fp

\int_new:N \g__broydensolve_iterations_int
\int_new:N \l__broydensolve_N_int

\seq_new:N \l__broydensolve_coordinates_seq
\seq_new:N \l__broydensolve_coords_seq
\seq_new:N \l__broydensolve_func_seq
\seq_new:N \l__broydensolve_init_seq
\seq_new:N \l__broydensolve_var_seq
\seq_new:N \l__broydensolve_var_N_seq

\tl_new:N \l__broydensolve_func_tl
\tl_new:N \l__broydensolve_name_tl

%%> \subsection{Keys}

\keys_define:nn { broydensolve }
  {
    abs-approx-error .fp_set:N = \l__broydensolve_abs_approx_error_fp ,
    abs-approx-error .initial:n = 10^-3 ,
    coordinates .bool_set:N = \l__broydensolve_coordinates_bool ,
    func .tl_set:N = \l__broydensolve_funcs_tl ,
    func-error .fp_set:N = \l__broydensolve_func_error_fp ,
    func-error .initial:n = 10^-3 ,
    init .code:n = \exp_args:NNe \seq_set_from_clist:Nn \l__broydensolve_init_seq {#1} ,
    iterations .int_set:N = \l__broydensolve_iterations_int ,
    iterations .initial:n = 5 ,
    rel-approx-error .fp_set:N = \l__broydensolve_rel_approx_error_fp ,
    rel-approx-error .initial:n = 10^-3 ,
    stop-crit .str_set_e:N = \l__broydensolve_stop_crit_str ,
    stop-crit .initial:n = rel-approx-error ,
    var .code:n = \exp_args:NNe \seq_set_from_clist:Nn \l__broydensolve_var_seq {#1} ,
    var .initial:n = { x , y , z }
  }

%%> \subsection{Functions}

\cs_new:Npn \__broydensolve_add:n #1
  { ( #1 < 0 ? ( #1 + 2 * pi ) : ( #1 ) ) }

\cs_new_protected:Npn \__broydensolve_add_name:N #1
  {
    \tl_if_empty:nF {#1}
      {
        \cs_if_exist:cTF { __fp_parse_word_#1:N }
          { \tl_build_put_right:Nn \l__broydensolve_func_tl {#1} }
          {
            \bool_if:NTF \l__broydensolve_coordinates_bool
              {
                \tl_set:Ne \l__broydensolve_name_tl { \tl_range:nnn {#1} { 1 } { -2 } }
                \seq_if_in:NVTF \l__broydensolve_coordinates_seq \l__broydensolve_name_tl
                  { \tl_build_put_right:Nn \l__broydensolve_func_tl { \cs:w l__broydensolve_var_#1_fp \cs_end: } }
                  {
                    \seq_if_in:NVF \l__broydensolve_coords_seq \l__broydensolve_name_tl
                      {
                        \path let \p { l__broydensolve_coord } = ( \l__broydensolve_name_tl ) in
                          [
                            / utils / exec =
                              {
                                \fp_gset:Nn \g__broydensolve_coord_x_fp
                                  {
                                    ( \pgf@yy * \x { l__broydensolve_coord } - \pgf@yx * \y { l__broydensolve_coord } )
                                    / \l__broydensolve_det_fp
                                  }
                                \fp_gset:Nn \g__broydensolve_coord_y_fp
                                  {
                                    ( \pgf@xx * \y { l__broydensolve_coord } - \pgf@xy * \x { l__broydensolve_coord } )
                                    / \l__broydensolve_det_fp
                                  }
                              }
                          ] ;
                        \fp_zero_new:c { l__broydensolve_coord_\l__broydensolve_name_tl x_fp }
                        \fp_zero_new:c { l__broydensolve_coord_\l__broydensolve_name_tl y_fp }
                        \fp_set_eq:cN { l__broydensolve_coord_\l__broydensolve_name_tl x_fp } \g__broydensolve_coord_x_fp
                        \fp_set_eq:cN { l__broydensolve_coord_\l__broydensolve_name_tl y_fp } \g__broydensolve_coord_y_fp
                        \seq_put_right:NV \l__broydensolve_coords_seq \l__broydensolve_name_tl
                      }
                    \tl_build_put_right:Ne \l__broydensolve_func_tl { \fp_use:c { l__broydensolve_coord_#1_fp } }
                  }
              }
              { \tl_build_put_right:Nn \l__broydensolve_func_tl { \cs:w l__broydensolve_var_#1_fp \cs_end: } }
          }
      }
  }

\cs_new:Npn \__broydensolve_atan:nnn #1#2#3
  { atan ( \clist_item:nn {#1} {#2} y - \clist_item:nn {#1} {#3} y , \clist_item:nn {#1} {#2} x - \clist_item:nn {#1} {#3} x ) }

\cs_new_protected:Npn \__broydensolve_init:nn #1#2
  {
    \fp_gzero_new:c { g__broydensolve_x_0_#2_fp }
    \fp_gset:cn { g__broydensolve_x_0_#2_fp } {#1}
    \fp_zero_new:c { l__broydensolve_var_#2_fp }
    \fp_set:cn { l__broydensolve_var_#2_fp } {#1}
  }

\cs_new_protected:Npn \__broydensolve_stop_crit:
  {
    \fp_set:Nn \l__broydensolve_aux_fp { abs ( \seq_use:Nn \l__broydensolve_func_seq { ) + abs ( } ) }
    \fp_compare:nNnTF { \l__broydensolve_aux_fp } > { 0 }
      {
        \str_case:VnF \l__broydensolve_stop_crit_str
          {
            { abs-approx-error }
              {
                \int_if_zero:nF { \g__broydensolve_iterations_int }
                  {
                    \fp_zero:N \l__broydensolve_norm_h_fp
                    \int_step_inline:nn { \l__broydensolve_N_int }
                      { \fp_add:Nn \l__broydensolve_norm_h_fp { abs ( \cs:w l__broydensolve_h_##1_fp \cs_end: ) } }
                    \bool_set:Nn \l__broydensolve_do_bool
                      { \fp_compare_p:nNn { \l__broydensolve_norm_h_fp } < { \l__broydensolve_abs_approx_error_fp } }
                  }
              }
            { func-error }
              {
                \bool_set:Nn \l__broydensolve_do_bool
                  { \fp_compare_p:nNn { \l__broydensolve_aux_fp } < { \l__broydensolve_func_error_fp } }
              }
            { iterations }
              {
                \bool_set:Nn \l__broydensolve_do_bool
                  { \int_compare_p:nNn { \g__broydensolve_iterations_int } = { \l__broydensolve_iterations_int } }
              }
            { rel-approx-error }
              {
                \int_if_zero:nF { \g__broydensolve_iterations_int }
                  {
                    \fp_zero:N \l__broydensolve_norm_h_fp
                    \fp_zero:N \l__broydensolve_norm_x_fp
                    \seq_map_indexed_inline:Nn \l__broydensolve_var_N_seq
                      {
                        \fp_add:Nn \l__broydensolve_norm_h_fp { abs ( \cs:w l__broydensolve_h_##1_fp \cs_end: ) }
                        \fp_add:Nn \l__broydensolve_norm_x_fp { abs ( \cs:w l__broydensolve_var_##2_fp \cs_end: ) }
                      }
                    \bool_set:Nn \l__broydensolve_do_bool
                      {
                        \fp_compare_p:nNn
                          { \l__broydensolve_norm_h_fp } < { \l__broydensolve_rel_approx_error_fp * \l__broydensolve_norm_x_fp }
                      }
                  }
              }
          }
          { \PackageError { broydensolve } { Wrong~value~for~key~stop-crit } {} }
      }
      { \bool_set_true:N \l__broydensolve_do_bool }
  }

%%> \subsection{Document commands}

\NewExpandableDocumentCommand \BroydenIterations {}
  { \int_use:N \g__broydensolve_iterations_int }

\NewExpandableDocumentCommand \BroydenRoot { O { \g__broydensolve_iterations_int } m }
  { \fp_use:c { g__broydensolve_x_\int_eval:n {#1}_#2_fp } }

\NewExpandableDocumentCommand \BroydenRoots {}
  { \g__broydensolve_roots_clist }

\NewDocumentCommand \BroydenSetup { m }
  { \keys_set:nn { broydensolve } {#1} }

\NewDocumentCommand \BroydenSolve { m }
  {
    \group_begin:
      \keys_set:nn { broydensolve } {#1}
      \DeclareExpandableDocumentCommand \ang { r () }
        {
          \__broydensolve_add:n
            {
              \int_case:nnF { \clist_count:n {##1} }
                {
                  { 1 } { atan ( \clist_item:nn {##1} { 1 } y , \clist_item:nn {##1} { 1 } x ) }
                  { 2 } { \__broydensolve_atan:nnn {##1} { 2 } { 1 } }
                  { 3 } { \__broydensolve_atan:nnn {##1} { 3 } { 2 } - \__broydensolve_atan:nnn {##1} { 1 } { 2 } }
                  { 4 } { \__broydensolve_atan:nnn {##1} { 4 } { 3 } - \__broydensolve_atan:nnn {##1} { 2 } { 1 } }
                }
                { \ang {} }
            }
        }
      \DeclareExpandableDocumentCommand \col { r () }
        {
          \int_compare:nNnTF { \clist_count:n {##1} } = { 3 }
            {
              \clist_item:nn {##1} { 1 } x * ( \clist_item:nn {##1} { 2 } y - \clist_item:nn {##1} { 3 } y )
              + \clist_item:nn {##1} { 2 } x * ( \clist_item:nn {##1} { 3 } y - \clist_item:nn {##1} { 1 } y )
              + \clist_item:nn {##1} { 3 } x * ( \clist_item:nn {##1} { 1 } y - \clist_item:nn {##1} { 2 } y )
            }
            { \col {} }
        }
      \DeclareExpandableDocumentCommand \dis { r () }
        {
          \int_case:nnF { \clist_count:n {##1} }
            {
              { 1 } { sqrt ( \clist_item:nn {##1} { 1 } x ^ 2 + \clist_item:nn {##1} { 1 } y ^ 2 ) }
              { 2 }
                {
                  sqrt
                    (
                      ( \clist_item:nn {##1} { 2 } x - \clist_item:nn {##1} { 1 } x ) ^ 2
                      + ( \clist_item:nn {##1} { 2 } y - \clist_item:nn {##1} { 1 } y ) ^ 2
                    )
                }
            }
            { \dis {} }
        }
      \int_set:Nn \l__broydensolve_N_int { \clist_count:e { \l__broydensolve_funcs_tl } }
      \bool_if:NTF \l__broydensolve_coordinates_bool
        {
          \seq_map_indexed_inline:Nn \l__broydensolve_var_seq
            {
              \seq_put_right:Nn \l__broydensolve_var_N_seq { ##2 x }
              \seq_put_right:Nn \l__broydensolve_var_N_seq { ##2 y }
              \seq_put_right:Nn \l__broydensolve_coordinates_seq {##2}
              \int_compare:nNnT {##1} = { \l__broydensolve_N_int / 2 }
                { \seq_map_break: }
            }
          \fp_set:Nn \l__broydensolve_det_fp { \pgf@yy * \pgf@xx - \pgf@yx * \pgf@xy }
        }
        {
          \seq_map_indexed_inline:Nn \l__broydensolve_var_seq
            {
              \seq_put_right:Nn \l__broydensolve_var_N_seq {##2}
              \int_compare:nNnT {##1} = { \l__broydensolve_N_int }
                { \seq_map_break: }
            }
        }
      \exp_args:Ne \clist_map_inline:nn { \l__broydensolve_funcs_tl }
        {
          \tl_build_begin:N \l__broydensolve_func_tl
          \tl_build_begin:N \l__broydensolve_name_tl
          \tl_map_inline:nn {##1}
            {
              \bool_lazy_and:nnTF
                { \bool_lazy_or_p:nn { \int_compare_p:nNn { `####1 } < { `a } } { \int_compare_p:nNn { `z } < { `####1 } } }
                { \bool_lazy_or_p:nn { \int_compare_p:nNn { `####1 } < { `A } } { \int_compare_p:nNn { `Z } < { `####1 } } }
                {
                  \tl_build_end:N \l__broydensolve_name_tl
                  \exp_args:NV \__broydensolve_add_name:N \l__broydensolve_name_tl
                  \tl_build_begin:N \l__broydensolve_name_tl
                  \tl_build_put_right:Nn \l__broydensolve_func_tl {####1}
                }
                { \tl_build_put_right:Nn \l__broydensolve_name_tl {####1} }
            }
          \tl_build_end:N \l__broydensolve_name_tl
          \exp_args:NV \__broydensolve_add_name:N \l__broydensolve_name_tl
          \tl_build_end:N \l__broydensolve_func_tl
          \seq_put_right:NV \l__broydensolve_func_seq \l__broydensolve_func_tl
        }
      \seq_map_pairwise_function:NNN \l__broydensolve_init_seq \l__broydensolve_var_N_seq \__broydensolve_init:nn
      \int_step_inline:nn { \l__broydensolve_N_int }
        {
          \int_step_inline:nn { \l__broydensolve_N_int }
            { \fp_zero_new:c { l__broydensolve_B_##1_####1_fp } }
          %set B to the identity matrix
          \fp_set:cn { l__broydensolve_B_##1_##1_fp } { 1 }
          \fp_zero_new:c { l__broydensolve_w_##1_fp }
          \fp_zero_new:c { l__broydensolve_h_##1_fp }
          \fp_zero_new:c { l__broydensolve_aux_##1_fp }
          \fp_zero_new:c { l__broydensolve_auxi_##1_fp }
        }
      \int_gzero:N \g__broydensolve_iterations_int
      \__broydensolve_stop_crit:
      \bool_until_do:Nn \l__broydensolve_do_bool
        {
          %set w=-f(x)
          \seq_map_indexed_inline:Nn \l__broydensolve_func_seq
            { \fp_set:cn { l__broydensolve_w_##1_fp } { - (##2) } }
          %set h=-B*f(x)=B*w
          \int_step_inline:nn { \l__broydensolve_N_int }
            {
              \fp_zero:c { l__broydensolve_h_##1_fp }
              \int_step_inline:nn { \l__broydensolve_N_int }
                {
                  \fp_add:cn { l__broydensolve_h_##1_fp }
                    { \cs:w l__broydensolve_B_##1_####1_fp \cs_end: * \cs:w l__broydensolve_w_####1_fp \cs_end: }
                }
            }
          %set the variable(s) to x+h
          \seq_map_indexed_inline:Nn \l__broydensolve_var_N_seq
            { \fp_add:cn { l__broydensolve_var_##2_fp } { \cs:w l__broydensolve_h_##1_fp \cs_end: } }
          %add f(x+h) to w so that w=f(x+h)-f(x)
          \seq_map_indexed_inline:Nn \l__broydensolve_func_seq
            { \fp_add:cn { l__broydensolve_w_##1_fp } {##2} }
          %update B
          \int_step_inline:nn { \l__broydensolve_N_int }
            {
              \fp_zero:c { l__broydensolve_aux_##1_fp }
              \int_step_inline:nn { \l__broydensolve_N_int }
                {
                  \fp_add:cn { l__broydensolve_aux_##1_fp }
                    { \cs:w l__broydensolve_B_##1_####1_fp \cs_end: * \cs:w l__broydensolve_w_####1_fp \cs_end: }
                }
            }
          \fp_zero:N \l__broydensolve_aux_fp
          \int_step_inline:nn { \l__broydensolve_N_int }
            {
              \fp_add:Nn \l__broydensolve_aux_fp { \cs:w l__broydensolve_h_##1_fp \cs_end: * \cs:w l__broydensolve_aux_##1_fp \cs_end: }
              \fp_sub:cn { l__broydensolve_aux_##1_fp } { \cs:w l__broydensolve_h_##1_fp \cs_end: }
            }
          \int_step_inline:nn { \l__broydensolve_N_int }
            {
              \fp_zero:c { l__broydensolve_auxi_##1_fp }
              \int_step_inline:nn { \l__broydensolve_N_int }
                {
                  \fp_add:cn { l__broydensolve_auxi_##1_fp }
                    { \cs:w l__broydensolve_h_####1_fp \cs_end: * \cs:w l__broydensolve_B_####1_##1_fp \cs_end: }
                }
            }
          \int_step_inline:nn { \l__broydensolve_N_int }
            {
              \int_step_inline:nn { \l__broydensolve_N_int }
                {
                  \fp_sub:cn { l__broydensolve_B_##1_####1_fp }
                    {
                      \cs:w l__broydensolve_aux_##1_fp \cs_end: * \cs:w l__broydensolve_auxi_####1_fp \cs_end:
                      / \l__broydensolve_aux_fp
                    }
                }
            }
          \int_gincr:N \g__broydensolve_iterations_int
          \seq_map_inline:Nn \l__broydensolve_var_N_seq
            {
              \fp_gzero_new:c { g__broydensolve_x_\int_use:N \g__broydensolve_iterations_int _##1_fp }
              \fp_gset_eq:cc { g__broydensolve_x_\int_use:N \g__broydensolve_iterations_int _##1_fp } { l__broydensolve_var_##1_fp }
            }
          \__broydensolve_stop_crit:
        }
      \clist_gclear:N \g__broydensolve_roots_clist
      \seq_map_inline:Nn \l__broydensolve_var_N_seq
        { \clist_gput_right:Ne \g__broydensolve_roots_clist { \BroydenRoot {##1} } }
      \bool_if:NT \l__broydensolve_coordinates_bool
        {
          \seq_map_inline:Nn \l__broydensolve_coordinates_seq
            { \coordinate (##1) at ( \BroydenRoot { ##1 x } , \BroydenRoot { ##1 y } ) ; }
        }
    \group_end:
  }

\endinput