mirror of
https://gitlab.com/libeigen/eigen.git
synced 2026-04-10 11:34:33 +08:00
big huge changes, so i dont remember everything.
* renaming, e.g. LU ---> FullPivLU * split tests framework: more robust, e.g. dont generate empty tests if a number is skipped * make all remaining tests use that splitting, as needed. * Fix 4x4 inversion (see stable branch) * Transform::inverse() and geo_transform test : adapt to new inverse() API, it was also trying to instantiate inverse() for 3x4 matrices. * CMakeLists: more robust regexp to parse the version number * misc fixes in unit tests
This commit is contained in:
@@ -29,7 +29,7 @@
|
||||
/** \ingroup QR_Module
|
||||
* \nonstableyet
|
||||
*
|
||||
* \class ColPivotingHouseholderQR
|
||||
* \class ColPivHouseholderQR
|
||||
*
|
||||
* \brief Householder rank-revealing QR decomposition of a matrix with column-pivoting
|
||||
*
|
||||
@@ -38,11 +38,11 @@
|
||||
* This class performs a rank-revealing QR decomposition using Householder transformations.
|
||||
*
|
||||
* This decomposition performs column pivoting in order to be rank-revealing and improve
|
||||
* numerical stability. It is slower than HouseholderQR, and faster than FullPivotingHouseholderQR.
|
||||
* numerical stability. It is slower than HouseholderQR, and faster than FullPivHouseholderQR.
|
||||
*
|
||||
* \sa MatrixBase::colPivotingHouseholderQr()
|
||||
* \sa MatrixBase::colPivHouseholderQr()
|
||||
*/
|
||||
template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
template<typename MatrixType> class ColPivHouseholderQR
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -68,11 +68,11 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
* \brief Default Constructor.
|
||||
*
|
||||
* The default constructor is useful in cases in which the user intends to
|
||||
* perform decompositions via ColPivotingHouseholderQR::compute(const MatrixType&).
|
||||
* perform decompositions via ColPivHouseholderQR::compute(const MatrixType&).
|
||||
*/
|
||||
ColPivotingHouseholderQR() : m_qr(), m_hCoeffs(), m_isInitialized(false) {}
|
||||
ColPivHouseholderQR() : m_qr(), m_hCoeffs(), m_isInitialized(false) {}
|
||||
|
||||
ColPivotingHouseholderQR(const MatrixType& matrix)
|
||||
ColPivHouseholderQR(const MatrixType& matrix)
|
||||
: m_qr(matrix.rows(), matrix.cols()),
|
||||
m_hCoeffs(std::min(matrix.rows(),matrix.cols())),
|
||||
m_isInitialized(false)
|
||||
@@ -94,8 +94,8 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
* \note The case where b is a matrix is not yet implemented. Also, this
|
||||
* code is space inefficient.
|
||||
*
|
||||
* Example: \include ColPivotingHouseholderQR_solve.cpp
|
||||
* Output: \verbinclude ColPivotingHouseholderQR_solve.out
|
||||
* Example: \include ColPivHouseholderQR_solve.cpp
|
||||
* Output: \verbinclude ColPivHouseholderQR_solve.out
|
||||
*/
|
||||
template<typename OtherDerived, typename ResultType>
|
||||
bool solve(const MatrixBase<OtherDerived>& b, ResultType *result) const;
|
||||
@@ -106,15 +106,15 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
*/
|
||||
const MatrixType& matrixQR() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
return m_qr;
|
||||
}
|
||||
|
||||
ColPivotingHouseholderQR& compute(const MatrixType& matrix);
|
||||
ColPivHouseholderQR& compute(const MatrixType& matrix);
|
||||
|
||||
const IntRowVectorType& colsPermutation() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
return m_cols_permutation;
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
*/
|
||||
inline int rank() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
return m_rank;
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
*/
|
||||
inline int dimensionOfKernel() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
return m_qr.cols() - m_rank;
|
||||
}
|
||||
|
||||
@@ -177,7 +177,7 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
*/
|
||||
inline bool isInjective() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
return m_rank == m_qr.cols();
|
||||
}
|
||||
|
||||
@@ -189,7 +189,7 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
*/
|
||||
inline bool isSurjective() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
return m_rank == m_qr.rows();
|
||||
}
|
||||
|
||||
@@ -200,7 +200,7 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
*/
|
||||
inline bool isInvertible() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
return isInjective() && isSurjective();
|
||||
}
|
||||
|
||||
@@ -215,7 +215,7 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
*/
|
||||
inline void computeInverse(MatrixType *result) const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
ei_assert(m_qr.rows() == m_qr.cols() && "You can't take the inverse of a non-square matrix!");
|
||||
solve(MatrixType::Identity(m_qr.rows(), m_qr.cols()), result);
|
||||
}
|
||||
@@ -247,23 +247,23 @@ template<typename MatrixType> class ColPivotingHouseholderQR
|
||||
#ifndef EIGEN_HIDE_HEAVY_CODE
|
||||
|
||||
template<typename MatrixType>
|
||||
typename MatrixType::RealScalar ColPivotingHouseholderQR<MatrixType>::absDeterminant() const
|
||||
typename MatrixType::RealScalar ColPivHouseholderQR<MatrixType>::absDeterminant() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
ei_assert(m_qr.rows() == m_qr.cols() && "You can't take the determinant of a non-square matrix!");
|
||||
return ei_abs(m_qr.diagonal().prod());
|
||||
}
|
||||
|
||||
template<typename MatrixType>
|
||||
typename MatrixType::RealScalar ColPivotingHouseholderQR<MatrixType>::logAbsDeterminant() const
|
||||
typename MatrixType::RealScalar ColPivHouseholderQR<MatrixType>::logAbsDeterminant() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
ei_assert(m_qr.rows() == m_qr.cols() && "You can't take the determinant of a non-square matrix!");
|
||||
return m_qr.diagonal().cwise().abs().cwise().log().sum();
|
||||
}
|
||||
|
||||
template<typename MatrixType>
|
||||
ColPivotingHouseholderQR<MatrixType>& ColPivotingHouseholderQR<MatrixType>::compute(const MatrixType& matrix)
|
||||
ColPivHouseholderQR<MatrixType>& ColPivHouseholderQR<MatrixType>::compute(const MatrixType& matrix)
|
||||
{
|
||||
int rows = matrix.rows();
|
||||
int cols = matrix.cols();
|
||||
@@ -333,12 +333,12 @@ ColPivotingHouseholderQR<MatrixType>& ColPivotingHouseholderQR<MatrixType>::comp
|
||||
|
||||
template<typename MatrixType>
|
||||
template<typename OtherDerived, typename ResultType>
|
||||
bool ColPivotingHouseholderQR<MatrixType>::solve(
|
||||
bool ColPivHouseholderQR<MatrixType>::solve(
|
||||
const MatrixBase<OtherDerived>& b,
|
||||
ResultType *result
|
||||
) const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
result->resize(m_qr.cols(), b.cols());
|
||||
if(m_rank==0)
|
||||
{
|
||||
@@ -378,9 +378,9 @@ bool ColPivotingHouseholderQR<MatrixType>::solve(
|
||||
|
||||
/** \returns the matrix Q as a sequence of householder transformations */
|
||||
template<typename MatrixType>
|
||||
typename ColPivotingHouseholderQR<MatrixType>::HouseholderSequenceType ColPivotingHouseholderQR<MatrixType>::matrixQ() const
|
||||
typename ColPivHouseholderQR<MatrixType>::HouseholderSequenceType ColPivHouseholderQR<MatrixType>::matrixQ() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "ColPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "ColPivHouseholderQR is not initialized.");
|
||||
return HouseholderSequenceType(m_qr, m_hCoeffs.conjugate());
|
||||
}
|
||||
|
||||
@@ -388,13 +388,13 @@ typename ColPivotingHouseholderQR<MatrixType>::HouseholderSequenceType ColPivoti
|
||||
|
||||
/** \return the column-pivoting Householder QR decomposition of \c *this.
|
||||
*
|
||||
* \sa class ColPivotingHouseholderQR
|
||||
* \sa class ColPivHouseholderQR
|
||||
*/
|
||||
template<typename Derived>
|
||||
const ColPivotingHouseholderQR<typename MatrixBase<Derived>::PlainMatrixType>
|
||||
MatrixBase<Derived>::colPivotingHouseholderQr() const
|
||||
const ColPivHouseholderQR<typename MatrixBase<Derived>::PlainMatrixType>
|
||||
MatrixBase<Derived>::colPivHouseholderQr() const
|
||||
{
|
||||
return ColPivotingHouseholderQR<PlainMatrixType>(eval());
|
||||
return ColPivHouseholderQR<PlainMatrixType>(eval());
|
||||
}
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
/** \ingroup QR_Module
|
||||
* \nonstableyet
|
||||
*
|
||||
* \class FullPivotingHouseholderQR
|
||||
* \class FullPivHouseholderQR
|
||||
*
|
||||
* \brief Householder rank-revealing QR decomposition of a matrix with full pivoting
|
||||
*
|
||||
@@ -38,11 +38,11 @@
|
||||
* This class performs a rank-revealing QR decomposition using Householder transformations.
|
||||
*
|
||||
* This decomposition performs a very prudent full pivoting in order to be rank-revealing and achieve optimal
|
||||
* numerical stability. The trade-off is that it is slower than HouseholderQR and ColPivotingHouseholderQR.
|
||||
* numerical stability. The trade-off is that it is slower than HouseholderQR and ColPivHouseholderQR.
|
||||
*
|
||||
* \sa MatrixBase::fullPivotingHouseholderQr()
|
||||
* \sa MatrixBase::fullPivHouseholderQr()
|
||||
*/
|
||||
template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
template<typename MatrixType> class FullPivHouseholderQR
|
||||
{
|
||||
public:
|
||||
|
||||
@@ -65,11 +65,11 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
/** \brief Default Constructor.
|
||||
*
|
||||
* The default constructor is useful in cases in which the user intends to
|
||||
* perform decompositions via FullPivotingHouseholderQR::compute(const MatrixType&).
|
||||
* perform decompositions via FullPivHouseholderQR::compute(const MatrixType&).
|
||||
*/
|
||||
FullPivotingHouseholderQR() : m_isInitialized(false) {}
|
||||
FullPivHouseholderQR() : m_isInitialized(false) {}
|
||||
|
||||
FullPivotingHouseholderQR(const MatrixType& matrix)
|
||||
FullPivHouseholderQR(const MatrixType& matrix)
|
||||
: m_isInitialized(false)
|
||||
{
|
||||
compute(matrix);
|
||||
@@ -89,8 +89,8 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
* \note The case where b is a matrix is not yet implemented. Also, this
|
||||
* code is space inefficient.
|
||||
*
|
||||
* Example: \include FullPivotingHouseholderQR_solve.cpp
|
||||
* Output: \verbinclude FullPivotingHouseholderQR_solve.out
|
||||
* Example: \include FullPivHouseholderQR_solve.cpp
|
||||
* Output: \verbinclude FullPivHouseholderQR_solve.out
|
||||
*/
|
||||
template<typename OtherDerived, typename ResultType>
|
||||
bool solve(const MatrixBase<OtherDerived>& b, ResultType *result) const;
|
||||
@@ -101,21 +101,21 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
*/
|
||||
const MatrixType& matrixQR() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
return m_qr;
|
||||
}
|
||||
|
||||
FullPivotingHouseholderQR& compute(const MatrixType& matrix);
|
||||
FullPivHouseholderQR& compute(const MatrixType& matrix);
|
||||
|
||||
const IntRowVectorType& colsPermutation() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
return m_cols_permutation;
|
||||
}
|
||||
|
||||
const IntColVectorType& rowsTranspositions() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
return m_rows_transpositions;
|
||||
}
|
||||
|
||||
@@ -155,7 +155,7 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
*/
|
||||
inline int rank() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
return m_rank;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
*/
|
||||
inline int dimensionOfKernel() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
return m_qr.cols() - m_rank;
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
*/
|
||||
inline bool isInjective() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
return m_rank == m_qr.cols();
|
||||
}
|
||||
|
||||
@@ -190,7 +190,7 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
*/
|
||||
inline bool isSurjective() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
return m_rank == m_qr.rows();
|
||||
}
|
||||
|
||||
@@ -201,7 +201,7 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
*/
|
||||
inline bool isInvertible() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
return isInjective() && isSurjective();
|
||||
}
|
||||
|
||||
@@ -216,7 +216,7 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
*/
|
||||
inline void computeInverse(MatrixType *result) const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
ei_assert(m_qr.rows() == m_qr.cols() && "You can't take the inverse of a non-square matrix!");
|
||||
solve(MatrixType::Identity(m_qr.rows(), m_qr.cols()), result);
|
||||
}
|
||||
@@ -249,23 +249,23 @@ template<typename MatrixType> class FullPivotingHouseholderQR
|
||||
#ifndef EIGEN_HIDE_HEAVY_CODE
|
||||
|
||||
template<typename MatrixType>
|
||||
typename MatrixType::RealScalar FullPivotingHouseholderQR<MatrixType>::absDeterminant() const
|
||||
typename MatrixType::RealScalar FullPivHouseholderQR<MatrixType>::absDeterminant() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
ei_assert(m_qr.rows() == m_qr.cols() && "You can't take the determinant of a non-square matrix!");
|
||||
return ei_abs(m_qr.diagonal().prod());
|
||||
}
|
||||
|
||||
template<typename MatrixType>
|
||||
typename MatrixType::RealScalar FullPivotingHouseholderQR<MatrixType>::logAbsDeterminant() const
|
||||
typename MatrixType::RealScalar FullPivHouseholderQR<MatrixType>::logAbsDeterminant() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
ei_assert(m_qr.rows() == m_qr.cols() && "You can't take the determinant of a non-square matrix!");
|
||||
return m_qr.diagonal().cwise().abs().cwise().log().sum();
|
||||
}
|
||||
|
||||
template<typename MatrixType>
|
||||
FullPivotingHouseholderQR<MatrixType>& FullPivotingHouseholderQR<MatrixType>::compute(const MatrixType& matrix)
|
||||
FullPivHouseholderQR<MatrixType>& FullPivHouseholderQR<MatrixType>::compute(const MatrixType& matrix)
|
||||
{
|
||||
int rows = matrix.rows();
|
||||
int cols = matrix.cols();
|
||||
@@ -342,12 +342,12 @@ FullPivotingHouseholderQR<MatrixType>& FullPivotingHouseholderQR<MatrixType>::co
|
||||
|
||||
template<typename MatrixType>
|
||||
template<typename OtherDerived, typename ResultType>
|
||||
bool FullPivotingHouseholderQR<MatrixType>::solve(
|
||||
bool FullPivHouseholderQR<MatrixType>::solve(
|
||||
const MatrixBase<OtherDerived>& b,
|
||||
ResultType *result
|
||||
) const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
result->resize(m_qr.cols(), b.cols());
|
||||
if(m_rank==0)
|
||||
{
|
||||
@@ -393,9 +393,9 @@ bool FullPivotingHouseholderQR<MatrixType>::solve(
|
||||
|
||||
/** \returns the matrix Q */
|
||||
template<typename MatrixType>
|
||||
typename FullPivotingHouseholderQR<MatrixType>::MatrixQType FullPivotingHouseholderQR<MatrixType>::matrixQ() const
|
||||
typename FullPivHouseholderQR<MatrixType>::MatrixQType FullPivHouseholderQR<MatrixType>::matrixQ() const
|
||||
{
|
||||
ei_assert(m_isInitialized && "FullPivotingHouseholderQR is not initialized.");
|
||||
ei_assert(m_isInitialized && "FullPivHouseholderQR is not initialized.");
|
||||
// compute the product H'_0 H'_1 ... H'_n-1,
|
||||
// where H_k is the k-th Householder transformation I - h_k v_k v_k'
|
||||
// and v_k is the k-th Householder vector [1,m_qr(k+1,k), m_qr(k+2,k), ...]
|
||||
@@ -417,13 +417,13 @@ typename FullPivotingHouseholderQR<MatrixType>::MatrixQType FullPivotingHousehol
|
||||
|
||||
/** \return the full-pivoting Householder QR decomposition of \c *this.
|
||||
*
|
||||
* \sa class FullPivotingHouseholderQR
|
||||
* \sa class FullPivHouseholderQR
|
||||
*/
|
||||
template<typename Derived>
|
||||
const FullPivotingHouseholderQR<typename MatrixBase<Derived>::PlainMatrixType>
|
||||
MatrixBase<Derived>::fullPivotingHouseholderQr() const
|
||||
const FullPivHouseholderQR<typename MatrixBase<Derived>::PlainMatrixType>
|
||||
MatrixBase<Derived>::fullPivHouseholderQr() const
|
||||
{
|
||||
return FullPivotingHouseholderQR<PlainMatrixType>(eval());
|
||||
return FullPivHouseholderQR<PlainMatrixType>(eval());
|
||||
}
|
||||
|
||||
#endif // EIGEN_FULLPIVOTINGHOUSEHOLDERQR_H
|
||||
@@ -39,10 +39,10 @@
|
||||
* stored in a compact way compatible with LAPACK.
|
||||
*
|
||||
* Note that no pivoting is performed. This is \b not a rank-revealing decomposition.
|
||||
* If you want that feature, use FullPivotingHouseholderQR or ColPivotingHouseholderQR instead.
|
||||
* If you want that feature, use FullPivHouseholderQR or ColPivHouseholderQR instead.
|
||||
*
|
||||
* This Householder QR decomposition is faster, but less numerically stable and less feature-full than
|
||||
* FullPivotingHouseholderQR or ColPivotingHouseholderQR.
|
||||
* FullPivHouseholderQR or ColPivHouseholderQR.
|
||||
*
|
||||
* \sa MatrixBase::householderQr()
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user