\ determ.4th
\
\ Return the determinant of a real square matrix.
\ The input matrix is destroyed in the process.

\ Krishna Myneni, Creative Consulting for Research and Education
\ http://ccreweb.org
\
\ Revisions: 
\
\   2007-09-16 km; extract word determ from original kForth matrix
\                  package into its own module, and modify to use
\                  FSL-style matrices; also use ttester for testing 
\   2017-06-11 km; rewrote determ.4th, factoring code for
\                  clarity of algorithm and improving execution
\                  efficiency.
\   2023-12-05 km; use F+! to simplify DF_SUB_ROWS
\
\ determ is based on a similar routine from P.R. Bevington,
\   "Data Reduction and Error Analysis for the Physical Sciences",
\   1969, McGraw-Hill.
\

Begin-Module

0 value L
0 value Norder		\ order of matrix 
0 ptr arr{{		\ address of matrix ( ptr is same as VALUE in ANS-Forth)
variable roffs

Public:

: set-determ-params ( 'a N -- )
    to Norder  to arr{{
    Norder dfloats roffs !
;

\ find column with next non-zero element, starting at
\ row = col; column = col; return the column number, col',
\ with non-zero element, which will be equal to Norder
\ if no non-zero elements found.
: df_next_nonzero ( col -- col' )
    arr{{ swap 
    dup to L
    BEGIN
      2dup L }} F@ F0= 
      L Norder 1- < and
    WHILE
      L 1+ to L
    REPEAT
    2drop L
;

\ exchange the double floats at a1 and a2
: df_xchg ( a1 a2 -- )
    dup pad dfloat move
    2dup dfloat move
    drop pad swap dfloat move    
;

\ swap columns n and m of matrix
: df_swap_cols ( n m -- )
    2>r
    arr{{ 0 r> }} arr{{ 0 r> }}
    Norder 0 DO
      2dup df_xchg
      roffs @ + swap roffs @ +
    LOOP
    2drop
;

\ subtract scaled row L from rows below to put matrix
\ in diagonal form (only does needed parts of rows).
: df_sub_rows ( l -- )
    to L
    arr{{ L L }} f@
    Norder L 1+ DO
      arr{{ I L }} f@ fover f/ 
      Norder L 1+ DO
	arr{{ L I }} f@ fover f* fnegate
        arr{{ J I }} f+!
      LOOP
      fdrop
    LOOP
    fdrop
;

: determ ( 'a n -- fdet | a is the matrix, n is its order )
    set-determ-params
    1e   
    Norder 0 DO
      arr{{ I I }} F@  F0= IF  \ zero on diagonal
	\ Find next element in row which is non-zero
	I df_next_nonzero
	dup Norder = IF drop fdrop 0e LEAVE  THEN
	to L
	arr{{ I L }} F@  F0= IF  fdrop 0e LEAVE  THEN
        L I df_swap_cols
	fnegate
      THEN
	
      \ Cumulative product of diagonal elements
      arr{{ I I }} F@ F*

      \ Subtract scaled row I from lower rows
      I Norder 1- < IF  I df_sub_rows  THEN
    LOOP
;

End-Module

TEST-CODE? [IF]   \ test code ==============================================
[undefined] T{      [IF]  s" ttester.4th" included  [THEN]
BASE @
DECIMAL

1e-15 rel-near F!
1e-15 abs-near F!
set-near

2 2 FLOAT MATRIX m2{{
    3e  1e
    5e  2e
2 2 m2{{ }}fput

3 3 FLOAT MATRIX m3{{
    1e  2e  3e
    2e  1e  1e
    3e  1e  2e
3 3 m3{{ }}fput

4 4 FLOAT MATRIX m4{{
    2e  1e  4e  2e
    0e  2e  1e  2e
    0e  2e  1e  1e
    2e  0e  1e  0e
4 4 m4{{ }}fput

TESTING DETERM
t{ m2{{ 2 determ  ->   1e  r}t
t{ m3{{ 3 determ  ->  -4e  r}t
t{ m4{{ 4 determ  -> -10e  r}t  

BASE !
[THEN]
