%% 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