From 7cc6138d4e6f9b71886ee484e88ea1af0c996399 Mon Sep 17 00:00:00 2001 From: Martin Kronbichler Date: Thu, 28 May 2009 07:43:51 +0000 Subject: [PATCH] Provide some Lapack templates that can be used to perform a QR factorization. git-svn-id: https://svn.dealii.org/trunk@18883 0785d39b-7218-0410-832d-ea1e28bc413d --- deal.II/base/include/base/config.h.in | 24 ++ deal.II/base/include/base/table.h | 2 +- deal.II/configure | 103 +++++++- deal.II/configure.in | 1 + .../blastemplates/lapack_templates.h.in | 29 ++- deal.II/contrib/blastemplates/templates.pl | 8 +- .../lac/include/lac/full_matrix.templates.h | 2 +- deal.II/lac/include/lac/lapack_templates.h | 225 ++++++++++++++++-- 8 files changed, 366 insertions(+), 28 deletions(-) diff --git a/deal.II/base/include/base/config.h.in b/deal.II/base/include/base/config.h.in index b20cc67053..ac2f2772d7 100644 --- a/deal.II/base/include/base/config.h.in +++ b/deal.II/base/include/base/config.h.in @@ -262,6 +262,9 @@ /* Define to 1 if you have the `dgemv_' function. */ #undef HAVE_DGEMV_ +/* Define to 1 if you have the `dgeqrf_' function. */ +#undef HAVE_DGEQRF_ + /* Define to 1 if you have the `dgesvd_' function. */ #undef HAVE_DGESVD_ @@ -274,9 +277,18 @@ /* Define to 1 if you have the `dgetrs_' function. */ #undef HAVE_DGETRS_ +/* Define to 1 if you have the `dorgqr_' function. */ +#undef HAVE_DORGQR_ + +/* Define to 1 if you have the `dormqr_' function. */ +#undef HAVE_DORMQR_ + /* Define to 1 if you have the `dstev_' function. */ #undef HAVE_DSTEV_ +/* Define to 1 if you have the `dtrtrs_' function. */ +#undef HAVE_DTRTRS_ + /* Define to 1 if you have the header file. */ #undef HAVE_EPETRA_CRSGRAPH_H @@ -372,6 +384,9 @@ /* Define to 1 if you have the `sgemv_' function. */ #undef HAVE_SGEMV_ +/* Define to 1 if you have the `sgeqrf_' function. */ +#undef HAVE_SGEQRF_ + /* Define to 1 if you have the `sgesvd_' function. */ #undef HAVE_SGESVD_ @@ -384,6 +399,12 @@ /* Define to 1 if you have the `sgetrs_' function. */ #undef HAVE_SGETRS_ +/* Define to 1 if you have the `sorgqr_' function. */ +#undef HAVE_SORGQR_ + +/* Define to 1 if you have the `sormqr_' function. */ +#undef HAVE_SORMQR_ + /* Define to 1 if you have the `sstev_' function. */ #undef HAVE_SSTEV_ @@ -417,6 +438,9 @@ /* Define to 1 if you have the header file. */ #undef HAVE_STRING_H +/* Define to 1 if you have the `strtrs_' function. */ +#undef HAVE_STRTRS_ + /* Define to 1 if you have the header file. */ #undef HAVE_SYS_STAT_H diff --git a/deal.II/base/include/base/table.h b/deal.II/base/include/base/table.h index ea9ac58a02..2f96016d7b 100644 --- a/deal.II/base/include/base/table.h +++ b/deal.II/base/include/base/table.h @@ -729,7 +729,7 @@ class TableBase : public Subscriptor /** * A class representing a table with arbitrary but fixed number of * indices. This general template implements some additional functions - * over those provided bythe TableBase class, such as indexing + * over those provided by the TableBase class, such as indexing * functions taking the correct number of arguments, etc. * * Rather than this general template, these functions are implemented diff --git a/deal.II/configure b/deal.II/configure index f4be092e03..c46cb70390 100755 --- a/deal.II/configure +++ b/deal.II/configure @@ -1,5 +1,5 @@ #! /bin/sh -# From configure.in Revision: 18770 . +# From configure.in Revision: 18849 . # Guess values for system-dependent variables and create Makefiles. # Generated by GNU Autoconf 2.61 for deal.II 6.3.pre. # @@ -16493,6 +16493,107 @@ done + + + + +for ac_func in dgeqrf_ sgeqrf_ dormqr_ sormqr_ dorgqr_ sorgqr_ dtrtrs_ strtrs_ +do +as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh` +{ echo "$as_me:$LINENO: checking for $ac_func" >&5 +echo $ECHO_N "checking for $ac_func... $ECHO_C" >&6; } +if { as_var=$as_ac_var; eval "test \"\${$as_var+set}\" = set"; }; then + echo $ECHO_N "(cached) $ECHO_C" >&6 +else + cat >conftest.$ac_ext <<_ACEOF +/* confdefs.h. */ +_ACEOF +cat confdefs.h >>conftest.$ac_ext +cat >>conftest.$ac_ext <<_ACEOF +/* end confdefs.h. */ +/* Define $ac_func to an innocuous variant, in case declares $ac_func. + For example, HP-UX 11i declares gettimeofday. */ +#define $ac_func innocuous_$ac_func + +/* System header to define __stub macros and hopefully few prototypes, + which can conflict with char $ac_func (); below. + Prefer to if __STDC__ is defined, since + exists even on freestanding compilers. */ + +#ifdef __STDC__ +# include +#else +# include +#endif + +#undef $ac_func + +/* Override any GCC internal prototype to avoid an error. + Use char because int might match the return type of a GCC + builtin and then its argument prototype would still apply. */ +#ifdef __cplusplus +extern "C" +#endif +char $ac_func (); +/* The GNU C library defines this for functions which it implements + to always fail with ENOSYS. Some functions are actually named + something starting with __ and the normal name is an alias. */ +#if defined __stub_$ac_func || defined __stub___$ac_func +choke me +#endif + +int +main () +{ +return $ac_func (); + ; + return 0; +} +_ACEOF +rm -f conftest.$ac_objext conftest$ac_exeext +if { (ac_try="$ac_link" +case "(($ac_try" in + *\"* | *\`* | *\\*) ac_try_echo=\$ac_try;; + *) ac_try_echo=$ac_try;; +esac +eval "echo \"\$as_me:$LINENO: $ac_try_echo\"") >&5 + (eval "$ac_link") 2>conftest.er1 + ac_status=$? + grep -v '^ *+' conftest.er1 >conftest.err + rm -f conftest.er1 + cat conftest.err >&5 + echo "$as_me:$LINENO: \$? = $ac_status" >&5 + (exit $ac_status); } && { + test -z "$ac_cxx_werror_flag" || + test ! -s conftest.err + } && test -s conftest$ac_exeext && + $as_test_x conftest$ac_exeext; then + eval "$as_ac_var=yes" +else + echo "$as_me: failed program was:" >&5 +sed 's/^/| /' conftest.$ac_ext >&5 + + eval "$as_ac_var=no" +fi + +rm -f core conftest.err conftest.$ac_objext conftest_ipa8_conftest.oo \ + conftest$ac_exeext conftest.$ac_ext +fi +ac_res=`eval echo '${'$as_ac_var'}'` + { echo "$as_me:$LINENO: result: $ac_res" >&5 +echo "${ECHO_T}$ac_res" >&6; } +if test `eval echo '${'$as_ac_var'}'` = yes; then + cat >>confdefs.h <<_ACEOF +#define `echo "HAVE_$ac_func" | $as_tr_cpp` 1 +_ACEOF + +fi +done + + + + + for ac_func in dgetrs_ sgetrs_ dstev_ sstev_ do as_ac_var=`echo "ac_cv_func_$ac_func" | $as_tr_sh` diff --git a/deal.II/configure.in b/deal.II/configure.in index 484e7dcef3..0d919ac91b 100644 --- a/deal.II/configure.in +++ b/deal.II/configure.in @@ -550,6 +550,7 @@ fi AC_CHECK_FUNCS([daxpy_ saxpy_ dgemv_ sgemv_ dgeev_ sgeev_ dgeevx_ sgeevx_]) AC_CHECK_FUNCS([dgesvd_ sgesvd_ dgemm_ sgemm_ dgetrf_ sgetrf_ dgetri_ sgetri_]) +AC_CHECK_FUNCS([dgeqrf_ sgeqrf_ dormqr_ sormqr_ dorgqr_ sorgqr_ dtrtrs_ strtrs_]) AC_CHECK_FUNCS([dgetrs_ sgetrs_ dstev_ sstev_]) dnl ------------------------------------------------------------- diff --git a/deal.II/contrib/blastemplates/lapack_templates.h.in b/deal.II/contrib/blastemplates/lapack_templates.h.in index 83571349a3..3e0d135160 100644 --- a/deal.II/contrib/blastemplates/lapack_templates.h.in +++ b/deal.II/contrib/blastemplates/lapack_templates.h.in @@ -19,13 +19,32 @@ void dgemm_ (const char* transa, const char* transb, // Compute LU factorization void dgetrf_ (const int* m, const int* n, double* A, const int* lda, int* ipiv, int* info); -// Invert matrix from LU factorization -void dgetri_ (const int* n, double* A, const int* lda, - int* ipiv, double* inv_work, const int* lwork, int* info); // Apply forward/backward substitution to LU factorization void dgetrs_ (const char* trans, const int* n, const int* nrhs, const double* A, const int* lda, const int* ipiv, double* b, const int* ldb, int* info); +// Invert matrix from LU factorization +void dgetri_ (const int* n, double* A, const int* lda, + int* ipiv, double* inv_work, const int* lwork, int* info); + +// Compute QR factorization (Householder) +void dgeqrf_ (const int* m, const int* n, double* A, + const int* lda, double* tau, double* work, + const int* lwork, int* info); +// Compute vector Q^T B, where Q is the result from dgeqrf_ +void dormqr_ (const char* side, const char* trans, const int* m, + const int* n, const int* k, const double* A, const int* lda, + const double* tau, double* B, const int* ldb, + double* work, const int* lwork, int* info); +// Compute matrix Q from the result of dgeqrf_ +void dorgqr_ (const int* m, const int* n, const int* k, const double* A, + const int* lda, const double* tau, double* work, const int* lwork, + int* info); +// Compute Rx = b +void dtrtrs_ (const char* uplo, const char* trans, + const char* diag, const int* n, const int* n_rhs, + const double* A, const int* lda, double* B, const int* ldb, + int* info); // Compute eigenvalues and vectors void dgeev_ (const char* jobvl, const char* jobvr, @@ -47,6 +66,10 @@ void dgeevx_ (const char* balanc, const char* jobvl, const char* jobvr, double* rconde, double* rcondv, double* work, const int* lwork, int* iwork, int* info); +// Eigenvalues for a symmetric matrix +void dsyev_ (const char *jobz, const char *uplo, const int *n, + double *A, const int *lda, double *w, + double *work, const int *lwork, int *info); // Compute singular value decomposition void dgesvd_ (int* jobu, int* jobvt, const int* n, const int* m, double* A, const int* lda, diff --git a/deal.II/contrib/blastemplates/templates.pl b/deal.II/contrib/blastemplates/templates.pl index b11e3e7d39..d139352f7d 100644 --- a/deal.II/contrib/blastemplates/templates.pl +++ b/deal.II/contrib/blastemplates/templates.pl @@ -2,7 +2,7 @@ # $Id$ # Version: $Name$ # -# Copyright (C) 2005, 2006, 2007, 2008 by the deal authors +# Copyright (C) 2005, 2006, 2007, 2008, 2009 by the deal authors # # This file is subject to QPL and may not be distributed # without copyright and license information. Please refer @@ -20,12 +20,12 @@ print << 'EOT' // $Id$ // Version: $Name$ // -// This file was automatically generated from blas.h.in +// This file was automatically generated from lapack_templates.h.in // See blastemplates in the deal.II contrib directory // -// Copyright (C) 2005, 2006, 2007, 2008 by the deal authors +// Copyright (C) 2005, 2006, 2007, 2008, 2009 by the deal authors // -// This file is subject to QPL and may not be distributed +// This file is subject to QPL and may not be distributed // without copyright and license information. Please refer // to the file deal.II/doc/license.html for the text and // further information on this license. diff --git a/deal.II/lac/include/lac/full_matrix.templates.h b/deal.II/lac/include/lac/full_matrix.templates.h index e1f67a705e..936884e238 100644 --- a/deal.II/lac/include/lac/full_matrix.templates.h +++ b/deal.II/lac/include/lac/full_matrix.templates.h @@ -1512,7 +1512,7 @@ namespace internal void getrf (const int*, const int*, T*, const int*, int*, int*) { - Assert (false, LAPACKSupport::ExcMissing("dgetr for this data type")); + Assert (false, LAPACKSupport::ExcMissing("dgetrf for this data type")); } diff --git a/deal.II/lac/include/lac/lapack_templates.h b/deal.II/lac/include/lac/lapack_templates.h index 148ab3353c..a9646ab069 100644 --- a/deal.II/lac/include/lac/lapack_templates.h +++ b/deal.II/lac/include/lac/lapack_templates.h @@ -2,12 +2,12 @@ // $Id$ // Version: $Name$ // -// This file was automatically generated from blas.h.in +// This file was automatically generated from lapack_templates.h.in // See blastemplates in the deal.II contrib directory // -// Copyright (C) 2005, 2006, 2007, 2008 by the deal authors +// Copyright (C) 2005, 2006, 2007, 2008, 2009 by the deal authors // -// This file is subject to QPL and may not be distributed +// This file is subject to QPL and may not be distributed // without copyright and license information. Please refer // to the file deal.II/doc/license.html for the text and // further information on this license. @@ -54,11 +54,6 @@ void dgetrf_ (const int* m, const int* n, double* A, const int* lda, int* ipiv, int* info); void sgetrf_ (const int* m, const int* n, float* A, const int* lda, int* ipiv, int* info); -// Invert matrix from LU factorization -void dgetri_ (const int* n, double* A, const int* lda, - int* ipiv, double* inv_work, const int* lwork, int* info); -void sgetri_ (const int* n, float* A, const int* lda, - int* ipiv, float* inv_work, const int* lwork, int* info); // Apply forward/backward substitution to LU factorization void dgetrs_ (const char* trans, const int* n, const int* nrhs, const double* A, const int* lda, const int* ipiv, @@ -66,6 +61,43 @@ void dgetrs_ (const char* trans, const int* n, const int* nrhs, void sgetrs_ (const char* trans, const int* n, const int* nrhs, const float* A, const int* lda, const int* ipiv, float* b, const int* ldb, int* info); +// Invert matrix from LU factorization +void dgetri_ (const int* n, double* A, const int* lda, + int* ipiv, double* inv_work, const int* lwork, int* info); +void sgetri_ (const int* n, float* A, const int* lda, + int* ipiv, float* inv_work, const int* lwork, int* info); +// Compute QR factorization (Householder) +void dgeqrf_ (const int* m, const int* n, double* A, + const int* lda, double* tau, double* work, + const int* lwork, int* info); +void sgeqrf_ (const int* m, const int* n, float* A, + const int* lda, float* tau, float* work, + const int* lwork, int* info); +// Compute vector Q^T B, where Q is the result from dgeqrf_ +void dormqr_ (const char* side, const char* trans, const int* m, + const int* n, const int* k, const double* A, const int* lda, + const double* tau, double* B, const int* ldb, + double* work, const int* lwork, int* info); +void sormqr_ (const char* side, const char* trans, const int* m, + const int* n, const int* k, const float* A, const int* lda, + const float* tau, float* B, const int* ldb, + float* work, const int* lwork, int* info); +// Compute matrix Q from the result of dgeqrf_ +void dorgqr_ (const int* m, const int* n, const int* k, const double* A, + const int* lda, const double* tau, double* work, const int* lwork, + int* info); +void sorgqr_ (const int* m, const int* n, const int* k, const float* A, + const int* lda, const float* tau, float* work, const int* lwork, + int* info); +// Compute Rx = b +void dtrtrs_ (const char* uplo, const char* trans, + const char* diag, const int* n, const int* n_rhs, + const double* A, const int* lda, double* B, const int* ldb, + int* info); +void strtrs_ (const char* uplo, const char* trans, + const char* diag, const int* n, const int* n_rhs, + const float* A, const int* lda, float* B, const int* ldb, + int* info); // Compute eigenvalues and vectors void dgeev_ (const char* jobvl, const char* jobvr, const int* n, double* A, const int* lda, @@ -104,6 +136,13 @@ void sgeevx_ (const char* balanc, const char* jobvl, const char* jobvr, float* rconde, float* rcondv, float* work, const int* lwork, int* iwork, int* info); +// Eigenvalues for a symmetric matrix +void dsyev_ (const char *jobz, const char *uplo, const int *n, + double *A, const int *lda, double *w, + double *work, const int *lwork, int *info); +void ssyev_ (const char *jobz, const char *uplo, const int *n, + float *A, const int *lda, float *w, + float *work, const int *lwork, int *info); // Compute singular value decomposition void dgesvd_ (int* jobu, int* jobvt, const int* n, const int* m, double* A, const int* lda, @@ -253,6 +292,36 @@ getrf (const int*, const int*, float*, const int*, int*, int*) #endif +#ifdef HAVE_DGETRS_ +inline void +getrs (const char* trans, const int* n, const int* nrhs, const double* A, const int* lda, const int* ipiv, double* b, const int* ldb, int* info) +{ + dgetrs_ (trans,n,nrhs,A,lda,ipiv,b,ldb,info); +} +#else +inline void +getrs (const char*, const int*, const int*, const double*, const int*, const int*, double*, const int*, int*) +{ + Assert (false, LAPACKSupport::ExcMissing("dgetrs")); +} +#endif + + +#ifdef HAVE_SGETRS_ +inline void +getrs (const char* trans, const int* n, const int* nrhs, const float* A, const int* lda, const int* ipiv, float* b, const int* ldb, int* info) +{ + sgetrs_ (trans,n,nrhs,A,lda,ipiv,b,ldb,info); +} +#else +inline void +getrs (const char*, const int*, const int*, const float*, const int*, const int*, float*, const int*, int*) +{ + Assert (false, LAPACKSupport::ExcMissing("sgetrs")); +} +#endif + + #ifdef HAVE_DGETRI_ inline void getri (const int* n, double* A, const int* lda, int* ipiv, double* inv_work, const int* lwork, int* info) @@ -283,32 +352,122 @@ getri (const int*, float*, const int*, int*, float*, const int*, int*) #endif -#ifdef HAVE_DGETRS_ +#ifdef HAVE_DGEQRF_ inline void -getrs (const char* trans, const int* n, const int* nrhs, const double* A, const int* lda, const int* ipiv, double* b, const int* ldb, int* info) +geqrf (const int* m, const int* n, double* A, const int* lda, double* tau, double* work, const int* lwork, int* info) { - dgetrs_ (trans,n,nrhs,A,lda,ipiv,b,ldb,info); + dgeqrf_ (m,n,A,lda,tau,work,lwork,info); } #else inline void -getrs (const char*, const int*, const int*, const double*, const int*, const int*, double*, const int*, int*) +geqrf (const int*, const int*, double*, const int*, double*, double*, const int*, int*) { - Assert (false, LAPACKSupport::ExcMissing("dgetrs")); + Assert (false, LAPACKSupport::ExcMissing("dgeqrf")); } #endif -#ifdef HAVE_SGETRS_ +#ifdef HAVE_SGEQRF_ inline void -getrs (const char* trans, const int* n, const int* nrhs, const float* A, const int* lda, const int* ipiv, float* b, const int* ldb, int* info) +geqrf (const int* m, const int* n, float* A, const int* lda, float* tau, float* work, const int* lwork, int* info) { - sgetrs_ (trans,n,nrhs,A,lda,ipiv,b,ldb,info); + sgeqrf_ (m,n,A,lda,tau,work,lwork,info); } #else inline void -getrs (const char*, const int*, const int*, const float*, const int*, const int*, float*, const int*, int*) +geqrf (const int*, const int*, float*, const int*, float*, float*, const int*, int*) { - Assert (false, LAPACKSupport::ExcMissing("sgetrs")); + Assert (false, LAPACKSupport::ExcMissing("sgeqrf")); +} +#endif + + +#ifdef HAVE_DORMQR_ +inline void +ormqr (const char* side, const char* trans, const int* m, const int* n, const int* k, const double* A, const int* lda, const double* tau, double* B, const int* ldb, double* work, const int* lwork, int* info) +{ + dormqr_ (side,trans,m,n,k,A,lda,tau,B,ldb,work,lwork,info); +} +#else +inline void +ormqr (const char*, const char*, const int*, const int*, const int*, const double*, const int*, const double*, double*, const int*, double*, const int*, int*) +{ + Assert (false, LAPACKSupport::ExcMissing("dormqr")); +} +#endif + + +#ifdef HAVE_SORMQR_ +inline void +ormqr (const char* side, const char* trans, const int* m, const int* n, const int* k, const float* A, const int* lda, const float* tau, float* B, const int* ldb, float* work, const int* lwork, int* info) +{ + sormqr_ (side,trans,m,n,k,A,lda,tau,B,ldb,work,lwork,info); +} +#else +inline void +ormqr (const char*, const char*, const int*, const int*, const int*, const float*, const int*, const float*, float*, const int*, float*, const int*, int*) +{ + Assert (false, LAPACKSupport::ExcMissing("sormqr")); +} +#endif + + +#ifdef HAVE_DORGQR_ +inline void +orgqr (const int* m, const int* n, const int* k, const double* A, const int* lda, const double* tau, double* work, const int* lwork, int* info) +{ + dorgqr_ (m,n,k,A,lda,tau,work,lwork,info); +} +#else +inline void +orgqr (const int*, const int*, const int*, const double*, const int*, const double*, double*, const int*, int*) +{ + Assert (false, LAPACKSupport::ExcMissing("dorgqr")); +} +#endif + + +#ifdef HAVE_SORGQR_ +inline void +orgqr (const int* m, const int* n, const int* k, const float* A, const int* lda, const float* tau, float* work, const int* lwork, int* info) +{ + sorgqr_ (m,n,k,A,lda,tau,work,lwork,info); +} +#else +inline void +orgqr (const int*, const int*, const int*, const float*, const int*, const float*, float*, const int*, int*) +{ + Assert (false, LAPACKSupport::ExcMissing("sorgqr")); +} +#endif + + +#ifdef HAVE_DTRTRS_ +inline void +trtrs (const char* uplo, const char* trans, const char* diag, const int* n, const int* n_rhs, const double* A, const int* lda, double* B, const int* ldb, int* info) +{ + dtrtrs_ (uplo,trans,diag,n,n_rhs,A,lda,B,ldb,info); +} +#else +inline void +trtrs (const char*, const char*, const char*, const int*, const int*, const double*, const int*, double*, const int*, int*) +{ + Assert (false, LAPACKSupport::ExcMissing("dtrtrs")); +} +#endif + + +#ifdef HAVE_STRTRS_ +inline void +trtrs (const char* uplo, const char* trans, const char* diag, const int* n, const int* n_rhs, const float* A, const int* lda, float* B, const int* ldb, int* info) +{ + strtrs_ (uplo,trans,diag,n,n_rhs,A,lda,B,ldb,info); +} +#else +inline void +trtrs (const char*, const char*, const char*, const int*, const int*, const float*, const int*, float*, const int*, int*) +{ + Assert (false, LAPACKSupport::ExcMissing("strtrs")); } #endif @@ -373,6 +532,36 @@ geevx (const char*, const char*, const char*, const char*, const int*, float*, c #endif +#ifdef HAVE_DSYEV_ +inline void +syev (const char *jobz, const char *uplo, const int *n, double *A, const int *lda, double *w, double *work, const int *lwork, int *info) +{ + dsyev_ (char*jobz,char*uplo,int*n,double*A,int*lda,double*w,double*work,int*lwork,int*info); +} +#else +inline void +syev (const char *, const char *, const int *, double *, const int *, double *, double *, const int *, int *) +{ + Assert (false, LAPACKSupport::ExcMissing("dsyev")); +} +#endif + + +#ifdef HAVE_SSYEV_ +inline void +syev (const char *jobz, const char *uplo, const int *n, float *A, const int *lda, float *w, float *work, const int *lwork, int *info) +{ + ssyev_ (char*jobz,char*uplo,int*n,double*A,int*lda,double*w,double*work,int*lwork,int*info); +} +#else +inline void +syev (const char *, const char *, const int *, float *, const int *, float *, float *, const int *, int *) +{ + Assert (false, LAPACKSupport::ExcMissing("ssyev")); +} +#endif + + #ifdef HAVE_DGESVD_ inline void gesvd (int* jobu, int* jobvt, const int* n, const int* m, double* A, const int* lda, double* s, double* u, const int* ldu, double* vt, const int* ldvt, double* work, const int* lwork, int* info) -- 2.39.5