Fix TMATRIX handling for Python by using doTranspose member.

This commit is contained in:
Ryan Curtin
2022-12-01 09:47:17 -05:00
parent 6850fde50e
commit ce655c1721
5 changed files with 90 additions and 6 deletions
+1
View File
@@ -25,6 +25,7 @@ cdef extern from "<mlpack/core/util/io.hpp>" namespace "mlpack" nogil:
cdef extern from "<mlpack/bindings/python/mlpack/io_util.hpp>" \
namespace "mlpack::util" nogil:
void SetParam[T](Params, string, T&) nogil except +
void SetParam[T](Params, string, T&, bool) nogil except +
void SetParamPtr[T](Params, string, T*, bool) nogil except +
void SetParamWithInfo[T](Params, string, T&, const bool*) nogil except +
(T*) GetParamPtr[T](Params, string) nogil except +
+28 -1
View File
@@ -19,20 +19,47 @@
namespace mlpack {
namespace util {
// Utility functions to correctly handle transposed Armadillo matrices.
template<typename T>
inline void TransposeIfNeeded(
const std::string& identifier,
T& value,
bool transpose)
{
// No transpose needed for non-matrices.
return;
}
inline void TransposeIfNeeded(
const std::string& identifier,
arma::mat& value,
bool transpose)
{
if (transpose)
{
arma::inplace_trans(value);
}
}
/**
* Set the parameter to the given value.
*
* This function exists to work around Cython's lack of support for lvalue
* references.
*
* @param params Parameters object to use.
* @param identifier Name of parameter.
* @param value Value to set parameter to.
* @param transpose If true, and if T is a matrix type, the matrix will be
* transposed in-place.
*/
template<typename T>
inline void SetParam(util::Params& params,
const std::string& identifier,
T& value)
T& value,
bool transpose = false)
{
TransposeIfNeeded(identifier, value, transpose);
params.Get<T>(identifier) = std::move(value);
}
@@ -271,13 +271,18 @@ void PrintInputProcessing(
* param_name_tuple[0].shape = (param_name_tuple[0].size,)
* param_name_mat = arma_numpy.numpy_to_mat_s(param_name_tuple[0],
* param_name_tuple[1])
* SetParam[mat](p, \<const string\> 'param_name', dereference(param_name_mat))
* SetParam[mat](p, \<const string\> 'param_name', dereference(param_name_mat), True)
* p.SetPassed(\<const string\> 'param_name')
*
* The value of the final boolean passed to SetParam is determined by whether
* the matrix is transposed or not. That boolean is omitted if the parameter
* is a row or column.
*/
std::cout << prefix << "# Detect if the parameter was passed; set if so."
<< std::endl;
std::string name = GetValidName(d.name);
std::string transStr =
(d.noTranspose ? std::string("True") : std::string("False"));
if (!d.required)
{
@@ -299,7 +304,7 @@ void PrintInputProcessing(
<< "_tuple[0], " << name << "_tuple[1])" << std::endl;
std::cout << prefix << " SetParam[" << GetCythonType<T>(d)
<< "](p, <const string> '" << d.name << "', dereference("
<< name << "_mat))"<< std::endl;
<< name << "_mat))" << std::endl;
std::cout << prefix << " p.SetPassed(<const string> '" << d.name
<< "')" << std::endl;
std::cout << prefix << " del " << name << "_mat" << std::endl;
@@ -319,7 +324,7 @@ void PrintInputProcessing(
<< "_tuple[0], " << name << "_tuple[1])" << std::endl;
std::cout << prefix << " SetParam[" << GetCythonType<T>(d)
<< "](p, <const string> '" << d.name << "', dereference("
<< name << "_mat))"<< std::endl;
<< name << "_mat), " << transStr << ")" << std::endl;
std::cout << prefix << " p.SetPassed(<const string> '" << d.name
<< "')" << std::endl;
std::cout << prefix << " del " << name << "_mat" << std::endl;
@@ -343,7 +348,7 @@ void PrintInputProcessing(
<< "_tuple[0], " << name << "_tuple[1])" << std::endl;
std::cout << prefix << "SetParam[" << GetCythonType<T>(d)
<< "](p, <const string> '" << d.name << "', dereference("
<< name << "_mat))"<< std::endl;
<< name << "_mat))" << std::endl;
std::cout << prefix << "p.SetPassed(<const string> '" << d.name << "')"
<< std::endl;
std::cout << prefix << "del " << name << "_mat" << std::endl;
@@ -362,7 +367,7 @@ void PrintInputProcessing(
<< "_tuple[0], " << name << "_tuple[1])" << std::endl;
std::cout << prefix << "SetParam[" << GetCythonType<T>(d)
<< "](p, <const string> '" << d.name << "', dereference(" << name
<< "_mat))" << std::endl;
<< "_mat), " << transStr << ")" << std::endl;
std::cout << prefix << "p.SetPassed(<const string> '" << d.name << "')"
<< std::endl;
std::cout << prefix << "del " << name << "_mat" << std::endl;
@@ -557,6 +557,35 @@ class TestPythonBinding(unittest.TestCase):
self.assertEqual(output['umatrix_out'][2, 2], 26)
self.assertEqual(output['umatrix_out'][2, 3], 14)
def testTransMatrix(self):
"""
Test that we can correctly pass a matrix in that's specified with the
PARAM_TMATRIX_IN() macro, and it is correctly transposed.
"""
x = np.random.rand(20, 10)
test_python_binding(string_in='hello',
int_in=12,
double_in=4.0,
mat_req_in=[[1.0]],
col_req_in=[1.0],
matrix_in=x,
tmatrix_in=x)
def testTransMatrixForceCopy(self):
"""
The same test as above, but we force copies.
"""
x = np.random.rand(20, 10)
xt = copy.deepcopy(np.transpose(x))
test_python_binding(string_in='hello',
int_in=12,
double_in=4.0,
mat_req_in=[[1.0]],
col_req_in=[1.0],
matrix_in=x,
tmatrix_in=x,
copy_all_inputs=True)
def testCol(self):
"""
Test a column vector input parameter.
@@ -46,6 +46,7 @@ PARAM_FLAG("flag2", "Input flag, must not be specified.", "F");
PARAM_MATRIX_IN("matrix_in", "Input matrix.", "m");
PARAM_MATRIX_IN("smatrix_in", "Input matrix.", "");
PARAM_UMATRIX_IN("umatrix_in", "Input unsigned matrix.", "u");
PARAM_TMATRIX_IN("tmatrix_in", "Input transposed matrix.", "");
PARAM_COL_IN("col_in", "Input column.", "c");
PARAM_UCOL_IN("ucol_in", "Input unsigned column.", "");
PARAM_ROW_IN("row_in", "Input row.", "");
@@ -114,6 +115,27 @@ void BINDING_FUNCTION(util::Params& params, util::Timers& /* timer */)
"single element!");
}
// If a transposed input matrix is given, an input matrix should also be
// given, and one should be the transpose of the other.
if (params.Has("tmatrix_in"))
{
if (!params.Has("matrix_in"))
{
throw std::runtime_error("If tmatrix_in is specified, matrix_in must be "
"specified!");
}
arma::mat tmat = params.Get<arma::mat>("tmatrix_in");
std::cerr << "tmat has size " << tmat.n_rows << " x " << tmat.n_cols << "\n";
arma::mat mat = params.Get<arma::mat>("matrix_in");
std::cerr << "mat has size " << mat.n_rows << " x " << mat.n_cols << "\n";
if (!arma::approx_equal(tmat.t(), mat, "reldiff", 0.001))
{
throw std::runtime_error("tmatrix_in transposed not equal to matrix_in!");
}
}
// Input matrices should be at least 5 rows; the 5th row will be dropped and
// the 3rd row will be multiplied by two.
if (params.Has("matrix_in"))