44 std::vector<std::vector<double>> &outputEmbedding,
45 const std::vector<double> &inputMatrix,
48 int *insertionTimeForTopomap)
const {
50#ifndef TTK_ENABLE_SCIKIT_LEARN
62 std::vector<double> coordsTopomap(2 * nRows);
63 topomap.
execute<
double>(coordsTopomap.data(), insertionTimeForTopomap,
65 outputEmbedding.resize(2);
66 outputEmbedding[0].resize(nRows);
67 outputEmbedding[1].resize(nRows);
68 for(
int i = 0; i < nRows; i++) {
69 outputEmbedding[0][i] = coordsTopomap[2 * i];
70 outputEmbedding[1][i] = coordsTopomap[2 * i + 1];
79#ifdef TTK_ENABLE_TORCH
90 outputEmbedding[d].resize(nRows);
92 tcdr.execute(outputEmbedding, inputMatrix, nRows);
98 this->
printErr(
"Unavailable backend: Torch is required.");
103#ifdef TTK_ENABLE_SCIKIT_LEARN
104#ifndef TTK_ENABLE_KAMIKAZE
120 vector<PyObject *> gc;
128 PyObject *pNumberOfComponents;
129 PyObject *pNumberOfNeighbors;
131 PyObject *pIsDeterministic;
135 PyObject *pEmbedding;
137 PyObject *pLLEParams;
138 PyObject *pMDSParams;
139 PyObject *pTSNEParams;
140 PyObject *pISOParams;
141 PyObject *pPCAParams;
143 PyArrayObject *npArr;
144 PyArrayObject *npEmbedding;
148 if(PyArray_API ==
nullptr) {
149#ifndef __clang_analyzer__
153 if(PyArray_API ==
nullptr) {
158 const int numberOfDimensions = 2;
159 npy_intp dimensions[2]{nRows, nColumns};
161 std::vector<std::string> methodToString{
162 "SE",
"LLE",
"MDS",
"t-SNE",
"IsoMap",
"PCA"};
164 pArray = PyArray_SimpleNewFromData(numberOfDimensions, dimensions, NPY_DOUBLE,
165 const_cast<double *
>(inputMatrix.data()));
166#ifndef TTK_ENABLE_KAMIKAZE
168 this->
printErr(
"Python: failed to convert the array.");
169 goto collect_garbage;
172 gc.push_back(pArray);
174 npArr =
reinterpret_cast<PyArrayObject *
>(pArray);
176 pSys = PyImport_ImportModule(
"sys");
177#ifndef TTK_ENABLE_KAMIKAZE
179 this->
printErr(
"Python: failed to load the sys module.");
180 goto collect_garbage;
185 pPath = PyObject_GetAttrString(pSys,
"path");
186#ifndef TTK_ENABLE_KAMIKAZE
188 this->
printErr(
"Python: failed to get the path variable.");
189 goto collect_garbage;
195 modulePath =
VALUE(TTK_SCRIPTS_PATH);
199 this->
printMsg(
"Loading Python script from: " + modulePath);
200 PyList_Append(pPath, PyUnicode_FromString(modulePath.data()));
203 pNumberOfComponents = PyLong_FromLong(numberOfComponents);
204#ifndef TTK_ENABLE_KAMIKAZE
205 if(!pNumberOfComponents) {
206 this->
printErr(
"Python: cannot convert pNumberOfComponents.");
207 goto collect_garbage;
210 gc.push_back(pNumberOfComponents);
212 pNumberOfNeighbors = PyLong_FromLong(numberOfNeighbors);
213#ifndef TTK_ENABLE_KAMIKAZE
214 if(!pNumberOfNeighbors) {
215 this->
printErr(
"Python: cannot convert pNumberOfNeighbors.");
216 goto collect_garbage;
219 gc.push_back(pNumberOfNeighbors);
221 pMethod = PyLong_FromLong(
static_cast<long>(this->
Method));
222#ifndef TTK_ENABLE_KAMIKAZE
224 this->
printErr(
"Python: cannot convert pMethod.");
225 goto collect_garbage;
228 gc.push_back(pMethod);
232 "MDS is known to be instable when used with multiple threads");
235#ifndef TTK_ENABLE_KAMIKAZE
237 this->
printErr(
"Python: cannot convert pJobs.");
238 goto collect_garbage;
242 pIsDeterministic = PyLong_FromLong(
static_cast<long>(this->
IsDeterministic));
243#ifndef TTK_ENABLE_KAMIKAZE
244 if(!pIsDeterministic) {
245 this->
printErr(
"Python: cannot convert pIsDeterministic.");
246 goto collect_garbage;
251 pName = PyUnicode_FromString(
ModuleName.data());
252#ifndef TTK_ENABLE_KAMIKAZE
254 this->
printErr(
"Python: moduleName parsing failed.");
255 goto collect_garbage;
260 pModule = PyImport_Import(pName);
261#ifndef TTK_ENABLE_KAMIKAZE
263 this->
printErr(
"Python: module import failed.");
264 goto collect_garbage;
267 gc.push_back(pModule);
270 pFunc = PyObject_GetAttrString(pModule,
FunctionName.data());
271#ifndef TTK_ENABLE_KAMIKAZE
273 this->
printErr(
"Python: functionName parsing failed.");
274 goto collect_garbage;
277 if(!PyCallable_Check(pFunc)) {
278 this->
printErr(
"Python: function call failed.");
279 goto collect_garbage;
283 pSEParams = PyList_New(0);
284 PyList_Append(pSEParams, PyUnicode_FromString(
se_Affinity.data()));
285 PyList_Append(pSEParams, PyFloat_FromDouble(
se_Gamma));
286 PyList_Append(pSEParams, PyUnicode_FromString(
se_EigenSolver.data()));
288 pLLEParams = PyList_New(0);
290 PyList_Append(pLLEParams, PyUnicode_FromString(
lle_EigenSolver.data()));
291 PyList_Append(pLLEParams, PyFloat_FromDouble(
lle_Tolerance));
293 PyList_Append(pLLEParams, PyUnicode_FromString(
lle_Method.data()));
299 pMDSParams = PyList_New(0);
300 PyList_Append(pMDSParams, PyBool_FromLong(
mds_Metric));
301 PyList_Append(pMDSParams, PyLong_FromLong(
mds_Init));
303 PyList_Append(pMDSParams, PyLong_FromLong(
mds_Verbose));
304 PyList_Append(pMDSParams, PyFloat_FromDouble(
mds_Epsilon));
307 pTSNEParams = PyList_New(0);
314 PyList_Append(pTSNEParams, PyUnicode_FromString(
tsne_Metric.data()));
315 PyList_Append(pTSNEParams, PyUnicode_FromString(
tsne_Init.data()));
316 PyList_Append(pTSNEParams, PyLong_FromLong(
tsne_Verbose));
317 PyList_Append(pTSNEParams, PyUnicode_FromString(
tsne_Method.data()));
318 PyList_Append(pTSNEParams, PyFloat_FromDouble(
tsne_Angle));
320 pISOParams = PyList_New(0);
321 PyList_Append(pISOParams, PyUnicode_FromString(
iso_EigenSolver.data()));
322 PyList_Append(pISOParams, PyFloat_FromDouble(
iso_Tolerance));
324 PyList_Append(pISOParams, PyUnicode_FromString(
iso_PathMethod.data()));
327 PyList_Append(pISOParams, PyUnicode_FromString(
iso_Metric.data()));
329 pPCAParams = PyList_New(0);
330 PyList_Append(pPCAParams, PyBool_FromLong(
pca_Copy));
331 PyList_Append(pPCAParams, PyBool_FromLong(
pca_Whiten));
332 PyList_Append(pPCAParams, PyUnicode_FromString(
pca_SVDSolver.data()));
333 PyList_Append(pPCAParams, PyFloat_FromDouble(
pca_Tolerance));
336 pParams = PyList_New(0);
337 gc.push_back(pParams);
339 PyList_Append(pParams, pSEParams);
340 PyList_Append(pParams, pLLEParams);
341 PyList_Append(pParams, pMDSParams);
342 PyList_Append(pParams, pTSNEParams);
343 PyList_Append(pParams, pISOParams);
344 PyList_Append(pParams, pPCAParams);
346 pReturn = PyObject_CallFunctionObjArgs(
347 pFunc, npArr, pMethod, pNumberOfComponents, pNumberOfNeighbors, pJobs,
348 pIsDeterministic, pParams, NULL);
349#ifndef TTK_ENABLE_KAMIKAZE
351 this->
printErr(
"Python: function returned invalid object.");
352 goto collect_garbage;
355 gc.push_back(pReturn);
357 pNRows = PyList_GetItem(pReturn, 0);
358#ifndef TTK_ENABLE_KAMIKAZE
360 this->
printErr(
"Python: function returned invalid number of rows");
361 goto collect_garbage;
365 pNColumns = PyList_GetItem(pReturn, 1);
366#ifndef TTK_ENABLE_KAMIKAZE
368 this->
printErr(
"Python: function returned invalid number of columns.");
369 goto collect_garbage;
373 pEmbedding = PyList_GetItem(pReturn, 2);
374#ifndef TTK_ENABLE_KAMIKAZE
376 this->
printErr(
"Python: function returned invalid embedding data.");
377 goto collect_garbage;
381 if(PyLong_AsLong(pNRows) == nRows
382 and PyLong_AsLong(pNColumns) == numberOfComponents) {
383 npEmbedding =
reinterpret_cast<PyArrayObject *
>(pEmbedding);
385 outputEmbedding.resize(numberOfComponents);
386 for(
int i = 0; i < numberOfComponents; ++i) {
387 outputEmbedding[i].resize(nRows);
388 if(PyArray_TYPE(npEmbedding) == NPY_FLOAT) {
389 float *c_out =
reinterpret_cast<float *
>(PyArray_DATA(npEmbedding));
390 for(
int j = 0; j < nRows; ++j)
391 outputEmbedding[i][j] = c_out[i * nRows + j];
392 }
else if(PyArray_TYPE(npEmbedding) == NPY_DOUBLE) {
393 double *c_out =
reinterpret_cast<double *
>(PyArray_DATA(npEmbedding));
394 for(
int j = 0; j < nRows; ++j)
395 outputEmbedding[i][j] = c_out[i * nRows + j];
404 this->
printMsg(
"Computed " + methodToString[
static_cast<int>(this->
Method)],
410#ifndef TTK_ENABLE_KAMIKAZE