PDL::L...lgebra(3pm)
π NAME π
okay so bestie, welcome to PDL::LinearAlgebra(3pm) β it lives in the User Contributed Perl Documentation section, and it is exactly what it sounds like: Linear Algebra utils for PDL πβ¨
π¦ SYNOPSIS π¦
use PDL::LinearAlgebra;
$a = random (100,100);
($U, $s, $V) = mdsvd($a);
like. three lines and you're already doing a singular value decomposition. iconic. π
πΈ DESCRIPTION πΈ
This module is your convenient little interface to PDL::LinearAlgebra::Real and PDL::LinearAlgebra::Complex. π«Ά
Now here's the tea bestie: BLAS and LAPACK use a column major ordering scheme, so some of the routines in here have to transpose matrices before they call the fortran routines and then transpose back afterwards (each routine's own documentation tells you when this is happening, check it π).
And if you want optimized code? Go straight to PDL::LinearAlgebra::Real and PDL::LinearAlgebra::Complex directly. No cap. π―
π₯ FUNCTIONS π₯
β¨ setlaerror β¨
Sets the action type used when an error gets encountered, and it returns the previous type. The available values are NO, WARN and BARF (these are predefined constants, bestie, they're just there for you π).
So like β say you're computing an inverse and the routine detects singularity. Depending on what you set, it can:
- silently return values from the computation (see the manuals for what you actually get π)
WARNyou about the singularity- or straight up
BARFπ
And BARF is the default value. She's dramatic. Periodt.
# h : x -> g(f(x))
$a = sequence(5,5);
$err = setlaerror(NO);
($b, $info)= f($a);
setlaerror($err);
$info ? barf "can't compute h" : return g($b);
see how you save the old error setting, do your thing, then put it back? main character error handling. π
π getlaerror π
Gets the action type used when an error is encountered. That's it, that's the whole vibe:
0 => NO,
1 => WARN,
2 => BARF
π issym π
PDL = issym(PDL, SCALAR|PDL(tol),SCALAR(hermitian))
tol : tolerance value, default: 1e-8 for double else 1e-5
hermitian : Hermitian = 1 | Symmetric = 0, default = 0;
Checks the symmetricity/Hermitianicity of a matrix. π¦
tolβ the tolerance value, default is1e-8for double, otherwise1e-5hermitianβHermitian = 1orSymmetric = 0, default is0
Supports broadcasting. β¨
π diag π
Returns the i-th diagonal if you hand it a matrix, or a matrix with the i-th diagonal if you hand it an entry. And the i-th diagonal it returns flows data back&forth β so yes bestie, it can be used as an lvalue sub if your perl supports it. π
Supports broadcasting.
PDL = diag(PDL, SCALAR(i), SCALAR(vector)))
i : i-th diagonal, default = 0
vector : create diagonal matrices by broadcasting over row vectors, default = 0
iβ which diagonal, default0vectorβ creates diagonal matrices by broadcasting over row vectors, default0
my $a = random(5,5);
my $diag = diag($a,2);
# If your perl support lvaluable subroutines.
$a->diag(-2) .= pdl(1,2,3);
# Construct a (5,5,5) PDL (5 matrices) with
# diagonals from row vectors of $a
$a->diag(0,1)
the .= assignment into a diagonal? that's the lvalue sub flexing. obsessed. π
π«Ά positivise π«Ά
Returns the entry pdl with the sign changed by row, so that the average of the positive signs is > 0. In other words: it broadcasts among dimension 1, and does row = -row whenever sum(sign(row)) < 0. π§
Heads up: this only makes sense for real ndarrays.
Works inplace. π₯
my $a = random(10,10);
$a -= 0.5;
$a->xchg(0,1)->inplace->positivise;
β mcrossprod β
Computes the cross-product of two matrices: A' x B. If you only give it one matrix, it takes B to be the same as A. Supports broadcasting. Uses crossprod or ccrossprod.
PDL = mcrossprod(PDL(A), (PDL(B))
my $a = random(10,10);
my $crossproduct = mcrossprod($a);
π« mrank π«
Computes the rank of a matrix using a singular value decomposition, and returns it as a plain Perl scalar. Straight from LAPACK, bestie. β¨
SCALAR = mrank(PDL, SCALAR(TOL))
TOL: tolerance value, default : mnorm(dims(PDL),'inf') * mnorm(PDL) * EPS
TOL is the tolerance value, and its default is mnorm(dims(PDL),'inf') * mnorm(PDL) * EPS.
my $a = random(10,10);
my $b = mrank($a, 1e-5);
π mnorm π
Computes the norm of a real or complex matrix. Supports broadcasting. π
PDL(norm) = mnorm(PDL, SCALAR(ord));
ord :
0|'inf' : Infinity norm
1|'one' : One norm
2|'two' : norm 2 (default)
3|'fro' : frobenius norm
0or'inf'β Infinity norm1or'one'β One norm2or'two'β norm 2, and this one is the default3or'fro'β frobenius norm
my $a = random(10,10);
my $norm = mnorm($a);
π― mdet π―
Computes the determinant of a general square matrix using LU factorization. Supports broadcasting. Uses getrf or cgetrf from LAPACK.
PDL(determinant) = mdet(PDL);
my $a = random(10,10);
my $det = mdet($a);
π mposdet π
Computes the determinant of a symmetric or Hermitian positive definite square matrix using Cholesky factorization. Supports broadcasting. Uses potrf or cpotrf from LAPACK.
(PDL, PDL) = mposdet(PDL, SCALAR)
SCALAR : UPPER = 0 | LOWER = 1, default = 0
so the scalar is UPPER = 0 or LOWER = 1, and the default is 0.
my $a = random(10,10);
my $det = mposdet($a);
πΊ mcond πΊ
Computes the condition number (two-norm) of a general matrix.
The condition number in two-n is defined as:
norm (a) * norm (inv (a)).
Uses a singular value decomposition. Supports broadcasting. π
PDL = mcond(PDL)
my $a = random(10,10);
my $cond = mcond($a);
π mrcond π
Estimates the reciprocal condition number of a general square matrix using LU factorization, in either the 1-norm or the infinity-norm.
The reciprocal condition number is defined as:
1/(norm (a) * norm (inv (a)))
Supports broadcasting. And heads up: it works on transposed array(s).
PDL = mrcond(PDL, SCALAR(ord))
ord :
0 : Infinity norm (default)
1 : One norm
0β Infinity norm, and this is the default1β One norm
my $a = random(10,10);
my $rcond = mrcond($a,1);
π¦ morth π¦
Returns an orthonormal basis of the range space of matrix A. Also, important: no broadcast, bestie. She does not do that.
PDL = morth(PDL(A), SCALAR(tol))
tol : tolerance for determining rank, default: 1e-8 for double else 1e-5
tol is the tolerance for determining rank, defaulting to 1e-8 for double, else 1e-5.
my $a = sequence(10,10);
my $ortho = morth($a, 1e-8);
π§ mnull π§
Returns an orthonormal basis of the null space of matrix A. And again: no broadcast.
PDL = mnull(PDL(A), SCALAR(tol))
tol : tolerance for determining rank, default: 1e-8 for double else 1e-5
tol is the tolerance for determining rank β 1e-8 for double, else 1e-5.
my $a = sequence(10,10);
my $null = mnull($a, 1e-8);
π minv π
Computes the inverse of a general square matrix using LU factorization. Supports inplace and broadcasting. Uses getrf and getri or cgetrf and cgetri from LAPACK, and it returns "inverse, info" in array context. π
PDL(inv) = minv(PDL)
my $a = random(10,10);
my $inv = minv($a);
π mtriinv π
Computes the inverse of a triangular matrix. Supports inplace and broadcasting. Uses trtri or ctrtri from LAPACK. Returns "inverse, info" in array context.
(PDL, PDL(info))) = mtriinv(PDL, SCALAR(uplo), SCALAR|PDL(diag))
uplo : UPPER = 0 | LOWER = 1, default = 0
diag : UNITARY DIAGONAL = 1, default = 0
uploβUPPER = 0orLOWER = 1, default0diagβUNITARY DIAGONAL = 1, default0
# Assume $a is upper triangular
my $a = random(10,10);
my $inv = mtriinv($a);
π msyminv π
Computes the inverse of a symmetric square matrix using the Bunch-Kaufman diagonal pivoting method. Supports inplace and broadcasting. Uses sytrf and sytri or csytrf and csytri from LAPACK, and returns "inverse, info" in array context.
(PDL, (PDL(info))) = msyminv(PDL, SCALAR|PDL(uplo))
uplo : UPPER = 0 | LOWER = 1, default = 0
uplo is UPPER = 0 or LOWER = 1, default 0.
# Assume $a is symmetric
my $a = random(10,10);
my $inv = msyminv($a);
π mposinv π
Computes the inverse of a symmetric positive definite square matrix using Cholesky factorization. Supports inplace and broadcasting. Uses potrf and potri or cpotrf and cpotri from LAPACK, and returns "inverse, info" in array context.
(PDL, (PDL(info))) = mposinv(PDL, SCALAR|PDL(uplo))
uplo : UPPER = 0 | LOWER = 1, default = 0
uplo is UPPER = 0 or LOWER = 1, default 0.
# Assume $a is symmetric positive definite
my $a = random(10,10);
$a = $a->crossprod($a);
my $inv = mposinv($a);
β¨ mpinv β¨
Computes the pseudo-inverse (Moore-Penrose) of a general matrix. And note: it works on a transposed array.
PDL(pseudo-inv) = mpinv(PDL, SCALAR(tol))
TOL: tolerance value, default : mnorm(dims(PDL),'inf') * mnorm(PDL) * EPS
TOL is the tolerance value, defaulting to mnorm(dims(PDL),'inf') * mnorm(PDL) * EPS.
my $a = random(5,10);
my $inv = mpinv($a);
π§ mlu π§
Computes LU factorization. Uses getrf or cgetrf from LAPACK and returns L, U, pivot and info. Works on a transposed array.
(PDL(l), PDL(u), PDL(pivot), PDL(info)) = mlu(PDL)
my $a = random(10,10);
($l, $u, $pivot, $info) = mlu($a);
πΈ mchol πΈ
Computes the Cholesky decomposition of a symmetric matrix, also known as the symmetric square root. If the inplace flag is set, it overwrites the leading upper or lower triangular part of A; otherwise it returns a triangular matrix. Returns "cholesky, info" in array context. Supports broadcasting. Uses potrf or cpotrf from LAPACK. π
PDL(Cholesky) = mchol(PDL, SCALAR)
SCALAR : UPPER = 0 | LOWER = 1, default = 0
the scalar is UPPER = 0 or LOWER = 1, default 0.
my $a = random(10,10);
$a = crossprod($a, $a);
my $u = mchol($a);
β mhessen β
Reduces a square matrix to Hessenberg form H and orthogonal matrix Q.
It reduces a general matrix A to upper Hessenberg form H by an orthogonal similarity transformation:
Q' x A x Q = H
or
A = Q x H x Q'
Uses gehrd and orghr or cgehrd and cunghr from LAPACK, and returns "H" in scalar context, else "H" and "Q". Works on a transposed array.
(PDL(h), (PDL(q))) = mhessen(PDL)
my $a = random(10,10);
($h, $q) = mhessen($a);
π« mschur π«
Computes the Schur form, and yes, it works inplace.
A = Z x T x Z'
Supports broadcasting for unordered eigenvalues. Uses gees or cgees from LAPACK and returns schur(T) in scalar context. Works on transposed array(s).
( PDL(schur), (PDL(eigenvalues), (PDL(left schur vectors), PDL(right schur vectors), $sdim), $info) ) = mschur(PDL(A), SCALAR(schur vector),SCALAR(left eigenvector), SCALAR(right eigenvector),SCALAR(select_func), SCALAR(backtransform), SCALAR(norm))
schur vector : Schur vectors returned, none = 0 | all = 1 | selected = 2, default = 0
left eigenvector : Left eigenvectors returned, none = 0 | all = 1 | selected = 2, default = 0
right eigenvector : Right eigenvectors returned, none = 0 | all = 1 | selected = 2, default = 0
select_func : Select_func is used to select eigenvalues to sort
to the top left of the Schur form.
An eigenvalue is selected if PerlInt select_func(w) is true;
(the inputs are converted to complex ndarrays for you)
Note that a selected complex eigenvalue may no longer
satisfy select_func(w) = 1 after ordering, since
ordering may change the value of complex eigenvalues
(especially if the eigenvalue is ill-conditioned).
All eigenvalues/vectors are selected if select_func is undefined.
backtransform : Whether or not backtransforms eigenvectors to those of A.
Only supported if schur vectors are computed, default = 1.
norm : Whether or not computed eigenvectors are normalized to have Euclidean norm equal to
1 and largest component real, default = 1
Returned values :
Schur form T (SCALAR CONTEXT),
eigenvalues,
Schur vectors (Z) if requested,
left eigenvectors if requested
right eigenvectors if requested
sdim: Number of eigenvalues selected if select_func is defined.
info: Info output from gees/cgees.
let's break the args down bestie, one bullet each π«Ά
schur vectorβ are Schur vectors returned?none = 0,all = 1,selected = 2, default0left eigenvectorβ left eigenvectors returned?none = 0,all = 1,selected = 2, default0right eigenvectorβ right eigenvectors returned?none = 0,all = 1,selected = 2, default0select_funcβ used to select eigenvalues to sort to the top left of the Schur form. An eigenvalue is selected ifPerlInt select_func(w)is true (the inputs are converted to complex ndarrays for you π). Note that a selected complex eigenvalue may no longer satisfyselect_func(w) = 1after ordering, since ordering may change the value of complex eigenvalues (especially if the eigenvalue is ill-conditioned π). All eigenvalues/vectors are selected ifselect_funcis undefined.backtransformβ whether or not to backtransform eigenvectors to those ofA. Only supported if Schur vectors are computed. Default1.normβ whether or not computed eigenvectors are normalized to have Euclidean norm equal to1and largest component real. Default1.
And the returned values (just to restate the tea):
- Schur form
T(in SCALAR CONTEXT) eigenvalues- Schur vectors (
Z) if requested - left eigenvectors if requested
- right eigenvectors if requested
sdim: the number of eigenvalues selected ifselect_funcis definedinfo: the info output fromgees/cgees
my $a = random(10,10);
my $schur = mschur($a);
sub select {
my $w = shift;
# select "discrete time" eigenspace
return $w->Cabs < 1 ? 1 : 0;
}
my ($schur,$eigen,$svectors,$evectors) = mschur($a,1,1,0,\&select);
that select sub is lowkey elegant, she's out here picking the "discrete time" eigenspace and I respect it. π
π¦ mschurx π¦
Computes the Schur form, and it works inplace. Uses geesx or cgeesx from LAPACK and returns schur(T) in scalar context. Works on a transposed array.
mschurx πΈ (the rest of it, bestie)
( PDL(schur) (,PDL(eigenvalues)) (, PDL(schur vectors), HASH(result)) ) = mschurx(PDL, SCALAR(schur vector), SCALAR(left eigenvector), SCALAR(right eigenvector),SCALAR(select_func), SCALAR(sense), SCALAR(backtransform), SCALAR(norm))
Okay so here are the arguments, one by one, no skips π
schur vectorβ Schur vectors returned:none = 0|all = 1|selected = 2, default =0left eigenvectorβ Left eigenvectors returned:none = 0|all = 1|selected = 2, default =0right eigenvectorβ Right eigenvectors returned:none = 0|all = 1|selected = 2, default =0select_funcβselect_funcis used to select eigenvalues to sort to the top left of the Schur form. An eigenvalue is selected ifPerlInt select_func(w)is true; (the inputs are converted to complex ndarrays for you) Note that a selected complex eigenvalue may no longer satisfyselect_func(w) = 1after ordering, since ordering may change the value of complex eigenvalues (especially if the eigenvalue is ill-conditioned). All eigenvalues/vectors are selected ifselect_funcis undefined.senseβ Determines which reciprocal condition numbers will be computed.0: None are computed1: Computed for average of selected eigenvalues only2: Computed for selected right invariant subspace only3: Computed for both Ifselect_funcis undefined,senseis not used.
backtransformβ Whether or not backtransforms eigenvectors to those ofA. Only supported if schur vector are computed, default =1normβ Whether or not computed eigenvectors are normalized to have Euclidean norm equal to1and largest component real, default =1
Returned values β¨
- Schur form
T(SCALAR CONTEXT), - eigenvalues,
- Schur vectors if requested,
HASH{VL}: left eigenvectors if requestedHASH{VR}: right eigenvectors if requestedHASH{info}: info output fromgees/cgees.- if
select_funcis defined:HASH{n}: number of eigenvalues selected,HASH{rconde}: reciprocal condition numbers for the average of the selected eigenvalues if requested,HASH{rcondv}: reciprocal condition numbers for the selected right invariant subspace if requested.
my $a = random(10,10);
my $schur = mschurx($a);
sub select {
my $m = shift;
# select "discrete time" eigenspace
return $m->Cabs < 1 ? 1 : 0;
}
my ($schur,$eigen, $vectors,%ret) = mschurx($a,1,0,0,\&select);
mgschur π¦
Computes generalized Schur decomposition of the pair (A,B).
A = Q x S x Z'
B = Q x T x Z'
Uses gges or cgges from LAPACK. Works on transposed array. (iykyk π
)
( PDL(schur S), PDL(schur T), PDL(alpha), PDL(beta), HASH{result}) = mgschur(PDL(A), PDL(B), SCALAR(left schur vector),SCALAR(right schur vector),SCALAR(left eigenvector), SCALAR(right eigenvector), SCALAR(select_func), SCALAR(backtransform), SCALAR(scale))
left schur vectorβ Left Schur vectors returned:none = 0|all = 1|selected = 2, default =0right schur vectorβ Right Schur vectors returned:none = 0|all = 1|selected = 2, default =0left eigenvectorβ Left eigenvectors returned:none = 0|all = 1|selected = 2, default =0right eigenvectorβ Right eigenvectors returned:none = 0|all = 1|selected = 2, default =0select_funcβSelect_funcis used to select eigenvalues to sort to the top left of the Schur form. An eigenvaluew = wr(j)+sqrt(-1)*wi(j)is selected ifPerlInt select_func(alpha,beta)is true; (the inputs are converted to complex ndarrays for you) Note that a selected complex eigenvalue may no longer satisfyselect_func = 1after ordering, since ordering may change the value of complex eigenvalues (especially if the eigenvalue is ill-conditioned). All eigenvalues/vectors are selected ifselect_funcis undefined.backtransformβ Whether or not backtransforms eigenvectors to those of(A,B). Only supported if right and/or left schur vector are computed,scaleβ Whether or not computed eigenvectors are scaled so the largest component will haveabs(real part) + abs(imag. part) = 1, default =1
Returned values π
- Schur form
S, - Schur form
T, alpha,beta(eigenvalues =alpha/beta),HASH{info}: info output fromgges/cgges.HASH{SL}: left Schur vectors if requestedHASH{SR}: right Schur vectors if requestedHASH{VL}: left eigenvectors if requestedHASH{VR}: right eigenvectors if requestedHASH{n}: Number of eigenvalues selected ifselect_funcis defined.
my $a = random(10,10);
my $b = random(10,10);
my ($S,$T) = mgschur($a,$b);
sub select {
my ($alpha,$beta) = @_;
return $alpha->Cabs < abs($beta) ? 1 : 0;
}
my ($S, $T, $alpha, $beta, %res) = mgschur( $a, $b, 1, 1, 1, 1,\&select);
mgschurx π
Computes generalized Schur decomposition of the pair (A,B).
A = Q x S x Z'
B = Q x T x Z'
Uses ggesx or cggesx from LAPACK. Works on transposed array.
( PDL(schur S), PDL(schur T), PDL(alpha), PDL(beta), HASH{result}) = mgschurx(PDL(A), PDL(B), SCALAR(left schur vector),SCALAR(right schur vector),SCALAR(left eigenvector), SCALAR(right eigenvector), SCALAR(select_func), SCALAR(sense), SCALAR(backtransform), SCALAR(scale))
left schur vectorβ Left Schur vectors returned:none = 0|all = 1|selected = 2, default =0right schur vectorβ Right Schur vectors returned:none = 0|all = 1|selected = 2, default =0left eigenvectorβ Left eigenvectors returned:none = 0|all = 1|selected = 2, default =0right eigenvectorβ Right eigenvectors returned:none = 0|all = 1|selected = 2, default =0select_funcβSelect_funcis used to select eigenvalues to sort to the top left of the Schur form. An eigenvaluew = wr(j)+sqrt(-1)*wi(j)is selected ifPerlInt select_func(alpha,beta)is true; (the inputs are converted to complex ndarrays for you) Note that a selected complex eigenvalue may no longer satisfyselect_func = 1after ordering, since ordering may change the value of complex eigenvalues (especially if the eigenvalue is ill-conditioned). All eigenvalues/vectors are selected ifselect_funcis undefined.senseβ Determines which reciprocal condition numbers will be computed.0: None are computed1: Computed for average of selected eigenvalues only2: Computed for selected deflating subspaces only3: Computed for both Ifselect_funcis undefined,senseis not used.
backtransformβ Whether or not backtransforms eigenvectors to those of(A,B). Only supported if right and/or left schur vector are computed, default =1scaleβ Whether or not computed eigenvectors are scaled so the largest component will haveabs(real part) + abs(imag. part) = 1, default =1
Returned values π
- Schur form
S, - Schur form
T, alpha,beta(eigenvalues =alpha/beta),HASH{info}: info output fromgges/cgges.HASH{SL}: left Schur vectors if requestedHASH{SR}: right Schur vectors if requestedHASH{VL}: left eigenvectors if requestedHASH{VR}: right eigenvectors if requestedHASH{rconde}: reciprocal condition numbers for average of selected eigenvalues if requestedHASH{rcondv}: reciprocal condition numbers for selected deflating subspaces if requestedHASH{n}: Number of eigenvalues selected ifselect_funcis defined.
my $a = random(10,10);
my $b = random(10,10);
my ($S,$T) = mgschurx($a,$b);
sub select {
my ($alpha,$beta) = @_;
return $alpha->Cabs < abs($beta) ? 1 : 0;
}
my ($S, $T, $alpha, $beta, %res) = mgschurx( $a, $b, 1, 1, 1, 1,\&select,3);
mqr π
Computes QR decomposition. Handles complex data. Uses geqrf and orgqr or cgeqrf and cungqr from LAPACK and returns "Q" in scalar context. Works on transposed array.
(PDL(Q), PDL(R), PDL(info)) = mqr(PDL, SCALAR)
SCALAR : ECONOMIC = 0 | FULL = 1, default = 0
my $a = random(10,10);
my ( $q, $r ) = mqr($a);
# Can compute full decomposition if nrow > ncol
$a = random(5,7);
( $q, $r ) = $a->mqr(1);
mrq π
Computes RQ decomposition. Handles complex data. Uses gerqf and orgrq or cgerqf and cungrq from LAPACK and returns "Q" in scalar context. Works on transposed array.
(PDL(R), PDL(Q), PDL(info)) = mrq(PDL, SCALAR)
SCALAR : ECONOMIC = 0 | FULL = 1, default = 0
my $a = random(10,10);
my ( $r, $q ) = mrq($a);
# Can compute full decomposition if nrow < ncol
$a = random(5,7);
( $r, $q ) = $a->mrq(1);
mql π§
Computes QL decomposition. Handles complex data. Uses geqlf and orgql or cgeqlf and cungql from LAPACK and returns "Q" in scalar context. Works on transposed array.
(PDL(Q), PDL(L), PDL(info)) = mql(PDL, SCALAR)
SCALAR : ECONOMIC = 0 | FULL = 1, default = 0
my $a = random(10,10);
my ( $q, $l ) = mql($a);
# Can compute full decomposition if nrow > ncol
$a = random(5,7);
( $q, $l ) = $a->mql(1);
mlq β
Computes LQ decomposition. Handles complex data. Uses gelqf and orglq or cgelqf and cunglq from LAPACK and returns "Q" in scalar context. Works on transposed array.
( PDL(L), PDL(Q), PDL(info) ) = mlq(PDL, SCALAR)
SCALAR : ECONOMIC = 0 | FULL = 1, default = 0
my $a = random(10,10);
my ( $l, $q ) = mlq($a);
# Can compute full decomposition if nrow < ncol
$a = random(5,7);
( $l, $q ) = $a->mlq(1);
msolve π₯
Solves linear system of equations using LU decomposition.
A * X = B
Returns X in scalar context else X, LU, pivot vector and info. B is overwritten by X if its inplace flag is set. Supports broadcasting. Uses gesv or cgesv from LAPACK. Works on transposed arrays.
bestie heads up:Bgets overwritten byXif its inplace flag is set, so don't come crying when your originalBis gone π
(PDL(X), (PDL(LU), PDL(pivot), PDL(info))) = msolve(PDL(A), PDL(B) )
my $a = random(5,5);
my $b = random(10,5);
my $X = msolve($a, $b);
msolvex π
Solves linear system of equations using LU decomposition.
A * X = B
Can optionally equilibrate the matrix. Uses gesvx or cgesvx from LAPACK. Works on transposed arrays.
(PDL, (HASH(result))) = msolvex(PDL(A), PDL(B), HASH(options))
where the options are, bestie:
transposeβ solvesA' * X = B.0= false,1= true.equilibrateβ equilibratesAif necessary. The form of the equilibration gets handed back to you inHASH{'equilibration'}:0= no equilibration,1= row equilibration,2= column equilibration. Row scale factors show up inHASH{'row'}, column scale factors inHASH{'column'}.0= false,1= true.LUβ returns the lu decomposition inHASH{LU}.0= false,1= true.Aβ returns scaledAif equilibration was done, inHASH{A}.0= false,1= true.Bβ returns scaledBif equilibration was done, inHASH{B}.0= false,1= true.
Returned values π:
X(SCALAR CONTEXT)HASH{'pivot'}: pivot indice from LU factorizationHASH{'rcondition'}: reciprocal condition of the matrixHASH{'ferror'}: forward error boundHASH{'berror'}: componentwise relative backward errorHASH{'rpvgrw'}: reciprocal pivot growth factorHASH{'info'}: info, output fromgesvx
my $a = random(10,10);
my $b = random(5,10);
my %options = (
LU=>1,
equilibrate => 1,
);
my( $X, %result) = msolvex($a,$b,%options);
π mtrisolve
Solves a linear system of equations with a triangular matrix A. It's giving:
A * X = B or A' * X = B
B gets overwritten by X if its inplace flag is set. Supports broadcasting. Uses trtrs or ctrtrs from LAPACK. Works on transposed array(s).
(PDL(X), (PDL(info)) = mtrisolve(PDL(A), SCALAR(uplo), PDL(B), SCALAR(trans), SCALAR(diag))
uplo : UPPER = 0 | LOWER = 1
trans : NOTRANSPOSE = 0 | TRANSPOSE = 1, default = 0
uplo : UNITARY DIAGONAL = 1, default = 0
# Assume $a is upper triagonal
my $a = random(5,5);
my $b = random(5,10);
my $X = mtrisolve($a, 0, $b);
π¦ msymsolve
Solves a linear system of equations using the diagonal pivoting method with a symmetric matrix A. Literally:
A * X = B
Returns X in scalar context, else X, the block diagonal matrix D (and the multipliers), the pivot vector and info. B gets overwritten by X if its inplace flag is set. Supports broadcasting. Uses sysv or csysv from LAPACK. Works on transposed array(s). No cap.
(PDL(X), ( PDL(D), PDL(pivot), PDL(info) ) ) = msymsolve(PDL(A), SCALAR(uplo), PDL(B) )
uplo : UPPER = 0 | LOWER = 1, default = 0
# Assume $a is symmetric
my $a = random(5,5);
my $b = random(5,10);
my $X = msymsolve($a, 0, $b);
πΈ msymsolvex
Also solves a linear system of equations using the diagonal pivoting method with a symmetric matrix A. Same energy:
A * X = B
Uses sysvx or csysvx from LAPACK. Works on transposed array.
(PDL, (HASH(result))) = msymsolvex(PDL(A), SCALAR (uplo), PDL(B), SCALAR(d))
uplo : UPPER = 0 | LOWER = 1, default = 0
d : whether return diagonal matrix d and pivot vector
FALSE = 0 | TRUE = 1, default = 0
Returned values β¨:
X(SCALAR CONTEXT)HASH{'D'}: block diagonal matrixD(and the multipliers) (if requested)HASH{'pivot'}: pivot indice from LU factorization (if requested)HASH{'rcondition'}: reciprocal condition of the matrixHASH{'ferror'}: forward error boundHASH{'berror'}: componentwise relative backward errorHASH{'info'}: info, output fromsysvx
# Assume $a is symmetric
my $a = random(10,10);
my $b = random(5,10);
my ($X, %result) = msolvex($a, 0, $b);
π mpossolve
Solves a linear system of equations using Cholesky decomposition with a symmetric positive definite matrix A. Obsessed:
A * X = B
Returns X in scalar context, else X, U or L and info. B gets overwritten by X if its inplace flag is set. Supports broadcasting. Uses posv or cposv from LAPACK. Works on transposed array(s).
(PDL, (PDL, PDL, PDL)) = mpossolve(PDL(A), SCALAR(uplo), PDL(B) )
uplo : UPPER = 0 | LOWER = 1, default = 0
# asume $a is symmetric positive definite
my $a = random(5,5);
my $b = random(5,10);
my $X = mpossolve($a, 0, $b);
π§ mpossolvex
Solves a linear system of equations using Cholesky decomposition with a symmetric positive definite matrix A:
A * X = B
Can optionally equilibrate the matrix. Uses posvx or cposvx from LAPACK. Works on transposed array(s).
(PDL, (HASH(result))) = mpossolvex(PDL(A), SCARA(uplo), PDL(B), HASH(options))
uplo : UPPER = 0 | LOWER = 1, default = 0
where the options are:
equilibrateβ equilibratesAif necessary. The form of equilibration comes back inHASH{'equilibration'}:0= no equilibration,1= equilibration. Scale factors come back inHASH{'scale'}.0= false,1= true.U|Lβ returns Cholesky factorization inHASH{U}orHASH{L}.0= false,1= true.Aβ returns scaledAif equilibration was done, inHASH{A}.0= false,1= true.Bβ returns scaledBif equilibration was done, inHASH{B}.0= false,1= true.
Returned values π«Ά:
X(SCALAR CONTEXT)HASH{'rcondition'}: reciprocal condition of the matrixHASH{'ferror'}: forward error boundHASH{'berror'}: componentwise relative backward errorHASH{'info'}: info, output fromgesvx
# Assume $a is symmetric positive definite
my $a = random(10,10);
my $b = random(5,10);
my %options = (U=>1,
equilibrate => 1,
);
my ($X, %result) = msolvex($a, 0, $b,%opt);
π mlls
Solves overdetermined or underdetermined real linear systems using QR or LQ factorization.
If M > N in the M-by-N matrix A, it returns the residual sum of squares too. Uses gels or cgels from LAPACK. Works on transposed arrays.
PDL(X) = mlls(PDL(A), PDL(B), SCALAR(trans))
trans : NOTRANSPOSE = 0 | TRANSPOSE/CONJUGATE = 1, default = 0
$a = random(4,5);
$b = random(3,5);
($x, $res) = mlls($a, $b);
β mllsy
Computes the minimum-norm solution to a real linear least squares problem using a complete orthogonal factorization.
Uses gelsy or cgelsy from LAPACK. Works on transposed arrays.
( PDL(X), ( HASH(result) ) ) = mllsy(PDL(A), PDL(B))
Returned values π:
X(SCALAR CONTEXT)HASH{'A'}: complete orthogonal factorization ofAHASH{'jpvt'}: details of columns interchangesHASH{'rank'}: effective rank ofA
my $a = random(10,10);
my $b = random(10,10);
$X = mllsy($a, $b);
π mllss
Computes the minimum-norm solution to a real linear least squares problem using a singular value decomposition.
Uses gelss or gelsd from LAPACK. Works on transposed arrays.
( PDL(X), ( HASH(result) ) )= mllss(PDL(A), PDL(B), SCALAR(method))
method: specifies which method to use (see LAPACK for further details)
'(c)gelss' or '(c)gelsd', default = '(c)gelsd'
Returned values π₯:
X(SCALAR CONTEXT)HASH{'V'}: if method =(c)gelss, the right singular vectors, stored columnwiseHASH{'s'}: singular values from SVDHASH{'res'}: ifAhas full rank, the residual sum-of-squares for the solutionHASH{'rank'}: effective rank ofAHASH{'info'}: info output from method
my $a = random(10,10);
my $b = random(10,10);
$X = mllss($a, $b);
π mglm
Solves a general Gauss-Markov Linear Model (GLM) problem. Supports broadcasting. Uses ggglm or cggglm from LAPACK. Works on transposed arrays.
(PDL(x), PDL(y)) = mglm(PDL(a), PDL(b), PDL(d))
where d is the left hand side of the GLM equation.
my $a = random(8,10);
my $b = random(7,10);
my $d = random(10);
my ($x, $y) = mglm($a, $b, $d);
π mlse
Solves a linear equality-constrained least squares (LSE) problem. Uses gglse or cgglse from LAPACK. Works on transposed arrays.
(PDL(x), PDL(res2)) = mlse(PDL(a), PDL(b), PDL(c), PDL(d))
where
c: the right hand side vector for the least squares part of the LSE problem.d: the right hand side vector for the constrained equation.x: the solution of the LSE problem.res2: the residual sum of squares for the solution (returned only in array context)
my $a = random(5,4);
my $b = random(5,3);
my $c = random(4);
my $d = random(3);
my ($x, $res2) = mlse($a, $b, $c, $d);
π¦ meigen
Computes eigenvalues and, optionally, the left and/or right eigenvectors of a general square matrix (spectral decomposition). Eigenvectors are normalized (Euclidean norm = 1) and largest component real. The eigenvalues and eigenvectors returned are complex ndarrays. If only eigenvalues are requested, info is returned in array context. Supports broadcasting. Uses geev or cgeev from LAPACK. Works on transposed arrays.
(PDL(values), (PDL(LV), (PDL(RV)), (PDL(info)))) = meigen(PDL, SCALAR(left vector), SCALAR(right vector))
left vector : FALSE = 0 | TRUE = 1, default = 0
right vector : FALSE = 0 | TRUE = 1, default = 0
my $a = random(10,10);
my ( $eigenvalues, $left_eigenvectors, $right_eigenvectors ) = meigen($a,1,1);
π meigenx
Computes eigenvalues, one-norm and, optionally, the left and/or right eigenvectors of a general square matrix (spectral decomposition). Eigenvectors are normalized (Euclidean norm = 1) and largest component real. The eigenvalues and eigenvectors returned are complex ndarrays. Uses geevx or cgeevx from LAPACK. Works on transposed arrays.
(PDL(value), (PDL(lv), (PDL(rv)), HASH(result)), HASH(result)) = meigenx(PDL, HASH(options))
where the options are, and yes we're doing all of them π
vectorβ which eigenvectors to compute:'left'β computes left eigenvectors'right'β computes right eigenvectors'all'β computes left and right eigenvectors0β doesn't compute (default)
rconditionβ reciprocal condition numbers to compute (these get returned inHASH{'rconde'}for eigenvalues andHASH{'rcondv'}for eigenvectors):'value'β computes reciprocal condition numbers for eigenvalues'vector'β computes reciprocal condition numbers for eigenvectors'all'β computes reciprocal condition numbers for eigenvalues and eigenvectors0β doesn't compute (default)
errorβ specifies whether or not it computes the error bounds (returned inHASH{'eerror'}andHASH{'verror'}):
error bound = EPS * One-norm / rcond(e|v)
(reciprocal condition numbers for eigenvalues or eigenvectors must be computed).
1β returns error bounds0β not computed
scaleβ specifies whether or not it diagonaly scales the entry matrix (scale details returned inHASH:'scale'):1β scales0β Doesn't scale (default)
permuteβ specifies whether or not it permutes row and columns (permute details returned inHASH{'balance'}):1β permutes0β Doesn't permute (default)
schurβ specifies whether or not it returns the Schur form (returned inHASH{'schur'}):1β returns Schur form0β not returned
Returned values β¨
- eigenvalues (SCALAR CONTEXT),
- left eigenvectors if requested,
- right eigenvectors if requested,
HASH{'norm'}: One-norm of the matrixHASH{'info'}: Info: if > 0, the QR algorithm failed to compute all the eigenvalues (see syevx for further details)
ok so like, here's how you'd actually use it π«Ά
my $a = random(10,10);
my %options = ( rcondition => 'all',
vector => 'all',
error => 1,
scale => 1,
permute=>1,
schur => 1
);
my ( $eigenvalues, $left_eigenvectors, $right_eigenvectors, %result) = meigenx($a,%options);
print "Error bounds for eigenvalues:\n $eigenvalues\n are:\n". transpose($result{'eerror'}) unless $info;
mgeigen
Computes generalized eigenvalues and, optionally, the left and/or right generalized eigenvectors for a pair of N-by-N real nonsymmetric matrices (A,B). The alpha from ratio alpha/beta is a complex ndarray. Supports broadcasting. Uses ggev or cggev from LAPACK. Works on transposed arrays. no cap π§
( PDL(alpha), PDL(beta), ( PDL(LV), (PDL(RV) ), PDL(info)) = mgeigen(PDL(A),PDL(B) SCALAR(left vector), SCALAR(right vector))
left vector: FALSE = 0 | TRUE = 1, default = 0right vector: FALSE = 0 | TRUE = 1, default = 0
like, in real life:
my $a = random(10,10);
my $b = random(10,10);
my ( $alpha, $beta, $left_eigenvectors, $right_eigenvectors ) = mgeigen($a, $b,1, 1);
mgeigenx
Computes generalized eigenvalues, one-norms and, optionally, the left and/or right generalized eigenvectors for a pair of N-by-N real nonsymmetric matrices (A,B). The alpha from ratio alpha/beta is a complex ndarray. Uses ggevx or cggevx from LAPACK. Works on transposed arrays. it's giving β¨determined eigenvaluesβ¨
(PDL(alpha), PDL(beta), PDL(lv), PDL(rv), HASH(result) ) = mgeigenx(PDL(a), PDL(b), HASH(options))
where options are:
vectorβ eigenvectors to compute:'left'β computes left eigenvectors'right'β computes right eigenvectors'all'β computes left and right eigenvectors0β doesn't compute (default)
rconditionβ reciprocal condition numbers to compute (returned inHASH{'rconde'}for eigenvalues andHASH{'rcondv'}for eigenvectors):'value'β computes reciprocal condition numbers for eigenvalues'vector'β computes reciprocal condition numbers for eigenvectors'all'β computes reciprocal condition numbers for eigenvalues and eigenvectors0β doesn't compute (default)
errorβ specifies whether or not it computes the error bounds (returned inHASH{'eerror'}andHASH{'verror'}):
error bound = EPS * sqrt(one-norm(a)**2 + one-norm(b)**2) / rcond(e|v)
(reciprocal condition numbers for eigenvalues or eigenvectors must be computed).
1β returns error bounds0β not computed
scaleβ specifies whether or not it diagonaly scales the entry matrix (scale details returned inHASH:'lscale'and'rscale'):1β scales0β doesn't scale (default)
permuteβ specifies whether or not it permutes row and columns (permute details returned inHASH{'balance'}):1β permutes0β Doesn't permute (default)
schurβ specifies whether or not it returns the Schur forms (returned inHASH{'aschur'}andHASH{'bschur'}) (right or left eigenvectors must be computed.):1β returns Schur forms0β not returned
Returned values π
- alpha,
- beta,
- left eigenvectors if requested,
- right eigenvectors if requested,
HASH{'anorm'},HASH{'bnorm'}: One-norm of the matrix A and BHASH{'info'}: Info: if > 0, the QR algorithm failed to compute all the eigenvalues (see syevx for further details)
and the example, untouched, because copying is caring π
$a = random(10,10);
$b = random(10,10);
%options = (rcondition => 'all',
vector => 'all',
error => 1,
scale => 1,
permute=>1,
schur => 1
);
($alpha, $beta, $left_eigenvectors, $right_eigenvectors, %result) = mgeigenx($a, $b,%options);
print "Error bounds for eigenvalues:\n $eigenvalues\n are:\n". transpose($result{'eerror'}) unless $info;
msymeigen
Computes eigenvalues and, optionally eigenvectors of a real symmetric square or complex Hermitian matrix (spectral decomposition). The eigenvalues are computed from lower or upper triangular matrix. If only eigenvalues are requested, info is returned in array context. Supports broadcasting and works inplace if eigenvectors are requested. From LAPACK, uses syev or syevd for real and cheev or cheevd for complex. Works on transposed array(s). spectral decomposition?? we love a glow-up π
(PDL(values), (PDL(VECTORS)), PDL(info)) = msymeigen(PDL, SCALAR(uplo), SCALAR(vector), SCALAR(method))
uplo:UPPER= 0 |LOWER= 1, default = 0vector: FALSE = 0 | TRUE = 1, default = 0method:'syev'|'syevd'|'cheev'|'cheevd', default ='syevd'|'cheevd'
# Assume $a is symmetric
my $a = random(10,10);
my ( $eigenvalues, $eigenvectors ) = msymeigen($a,0,1, 'syev');
msymeigenx
Computes eigenvalues and, optionally eigenvectors of a symmetric square matrix (spectral decomposition). The eigenvalues are computed from lower or upper triangular matrix and can be selected by specifying a range. From LAPACK, uses syevx or syevr for real and cheevx or cheevr for complex. Works on transposed arrays. picky about your range? iconic, honestly π
(PDL(value), (PDL(vector)), PDL(n), PDL(info), (PDL(support)) ) = msymeigenx(PDL, SCALAR(uplo), SCALAR(vector), HASH(options))
uplo:UPPER= 0 |LOWER= 1, default = 0vector: FALSE = 0 | TRUE = 1, default = 0
where options are:
range_typeβ method for selecting eigenvalues:indice: range of indicesinterval: range of values0: find all eigenvalues and optionally all vectors
rangeβPDL(2), lower and upper bounds interval or smallest and largest indices1<=range<=Nfor indice
abstolβ specifies error tolerance for eigenvaluesmethodβ specifies which method to use (see LAPACK for further details):'syevx'(default)'syevr''cheevx'(default)'cheevr'
Returned values π
- eigenvalues (SCALAR CONTEXT),
- eigenvectors if requested,
- total number of eigenvalues found (n),
- info
- issupz or ifail (support) according to method used and returned info, for (sy|che)evx returns support only if info != 0
# Assume $a is symmetric
my $a = random(10,10);
my $overflow = lamch(9);
my $range = cat pdl(0),$overflow;
my $abstol = pdl(1.e-5);
my %options = (range_type=>'interval',
range => $range,
abstol => $abstol,
method=>'syevd');
my ( $eigenvalues, $eigenvectors, $n, $isuppz ) = msymeigenx($a,0,1, %options);
msymgeigen
Computes eigenvalues and, optionally eigenvectors of a real generalized symmetric-definite or Hermitian-definite eigenproblem. The eigenvalues are computed from lower or upper triangular matrix If only eigenvalues are requested, info is returned in array context. Supports broadcasting. From LAPACK, uses sygv or sygvd for real or chegv or chegvd for complex. Works on transposed array(s). the generalized one, bestie, don't mix them up π
(PDL(values), (PDL(vectors)), PDL(info)) = msymgeigen(PDL(a), PDL(b),SCALAR(uplo), SCALAR(vector), SCALAR(type), SCALAR(method))
uplo:UPPER= 0 |LOWER= 1, default = 0vector: FALSE = 0 | TRUE = 1, default = 0type:1:A * x = (lambda) * B * x2:A * B * x = (lambda) * x3:B * A * x = (lambda) * x- default = 1
method:'sygv'|'sygvd'for real or ,'chegv'|'chegvd'for complex, default ='sygvd'|'chegvd'
# Assume $a is symmetric
my $a = random(10,10);
my $b = random(10,10);
$b = $b->crossprod($b);
my ( $eigenvalues, $eigenvectors ) = msymgeigen($a, $b, 0, 1, 1, 'sygv');
msymgeigenx
Computes eigenvalues and, optionally eigenvectors of a real generalized symmetric-definite or Hermitian eigenproblem. The eigenvalues are computed from lower or upper triangular matrix and can be selected by specifying a range. Uses sygvx or cheevx from LAPACK. Works on transposed arrays. yes the "x" goes at the end, we stan a consistent naming convention π¦
(PDL(value), (PDL(vector)), PDL(info), PDL(n), (PDL(support)) ) = msymeigenx(PDL(a), PDL(b), SCALAR(uplo), SCALAR(vector), HASH(options))
uplo:UPPER= 0 |LOWER= 1, default = 0vector: FALSE = 0 | TRUE = 1, default = 0
where options are:
typeβ Specifies the problem type to be solved:1:A * x = (lambda) * B * x2:A * B * x = (lambda) * x3:B * A * x = (lambda) * x- default = 1
range_typeβ method for selecting eigenvalues:indice: range of indicesinterval: range of values0: find all eigenvalues and optionally all vectors
rangeβPDL(2), lower and upper bounds interval or smallest and largest indices1<=range<=Nfor indice
abstolβ specifies error tolerance for eigenvalues
Returned values β
- eigenvalues (SCALAR CONTEXT),
- eigenvectors if requested,
- total number of eigenvalues found (n),
- info
- ifail according to returned info (support).
okay so the msymgeigenx example is still vibing from before β here's the rest of it, no cliffhanger bestie:
# Assume $a is symmetric
my $a = random(10,10);
my $b = random(10,10);
$b = $b->crossprod($b);
my $overflow = lamch(9);
my $range = cat pdl(0),$overflow;
my $abstol = pdl(1.e-5);
my %options = (range_type=>'interval',
range => $range,
abstol => $abstol,
type => 1);
my ( $eigenvalues, $eigenvectors, $n, $isuppz ) = msymgeigenx($a, $b, 0,1, %options);
and that's the whole serve π okay moving ON, we have four more decompositions to girlboss through.
π mdsvd π
so this one computes the SVD using Cuppen's divide and conquer algorithm π it hands back singular values in scalar context, otherwise you get left (U), singular values, right (V' β which is hermitian for complex, iykyk) singular vectors and info. Supports broadcasting β¨ and if you're only asking for the singular values, info is only returned in array context. Under the hood it's gesdd or cgesdd from LAPACK, periodt.
(PDL(U), (PDL(s), PDL(Vt)), PDL(info)) = mdsvd(PDL, SCALAR(job))
jobβ0computes only singular values,1computes full SVD (squareUandVt),2computes SVD (singular values, right and left singular vectors). default =1
example time:
my $a = random(5,10);
my ($u, $s, $vt) = mdsvd($a);
print $u x gurney($s, $a->dims) x $vt;
πΈ msvd πΈ
computes SVD, iconic behaviour honestly. it can compute singular values, either U or Vt, or neither β your call bestie. returns singular values in scalar context, otherwise left (U), singular values, right (V' (hermitian for complex)) singular vector and info. Supports broadcasting π¦ and if only the singular values are requested, info gets returned in array context. uses gesvd or cgesvd from LAPACK.
( (PDL(U)), PDL(s), (PDL(Vt), PDL(info)) = msvd(PDL, SCALAR(jobu), SCALAR(jobv))
jobuβ0Doesn't compute U,1computes full SVD (square U),2computes right singular vectors. default =1jobvβ0Doesn't compute Vt,1computes full SVD (square Vt),2computes left singular vectors. default =1
another little demo for the girls:
my $a = random(10,10);
my ($u, $s, $vt) = msvd($a);
print $u x gurney($s, $a->dims) x $vt;
π¦ mgsvd π¦
computes the generalized (or quotient) singular value decomposition β¨ and here's the drama: if the effective rank of (A',B')' is 0, it returns only unitary V, U, Q. it handles complex data, uses ggsvd or cggsvd from LAPACK, and works on transposed arrays.
(PDL(sa), PDL(sb), %ret) = mgsvd(PDL(a), PDL(b), %HASH(options))
where the options are:
Vβ whether or not to computeV(boolean, returned inHASH{'V'})Uβ whether or not to computeU(boolean, returned inHASH{'U'})Qβ whether or not to computeQ(boolean, returned inHASH{'Q'})D1β whether or not to computeD1(boolean, returned inHASH{'D1'})D2β whether or not to computeD2(boolean, returned inHASH{'D2'})0Rβ whether or not to compute0R(boolean, returned inHASH{'0R'})Rβ whether or not to computeR(boolean, returned inHASH{'R'})Xβ whether or not to computeX(boolean, returned inHASH{'X'})allβ whether or not to compute all of the above. yes she said "all of it" π
and what you actually get back:
sa,sbβ singular value pairs ofAandB(generalized singular values =sa/sb)$ret{'rank'}β effective numerical rank of(A',B')'$ret{'info'}β info from(c)ggsvd
vibe check on the example:
my $a = random(5,5);
my $b = random(5,7);
my ($c, $s, %ret) = mgsvd($a, $b, X => 1);
π AUTHOR π
Copyright (C) GrΓ©gory Vanuxem 2005-2018. π«Ά
This library is free software; you can redistribute it and/or modify it under the terms of the Perl Artistic License, as in the file Artistic_2 in this distribution.
and that's the whole page babes, we made it ππ this was PDL::L...lgebra(3pm) in the perl v5.42.3 distribution, dated 2026-08-21 β screenshot it, send it to the group chat, and go do some linear algebra, you absolute main character ππβ¨
π¦ where this page comes from
- ubuntu stonking: libpdl-linearalgebra-perl 0.437-1build1 Β· Artistic-2
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms