Skip to content

Commit 66416c6

Browse files
Merge pull request #1105 from MClemot/TopologicallyConstrainedDR
[DimensionReduction] New backend
2 parents edad766 + 7a8344f commit 66416c6

File tree

18 files changed

+1642
-42
lines changed

18 files changed

+1642
-42
lines changed

ChangeLog.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
## TTK - ChangeLog
22
=
33
### dev
4-
- Distributed computation of persistent homology!
4+
- Cycle-aware dimensionality reduction (TopoAE++)
5+
- Distributed computation of persistent homology! (IEEE TPDS 2025)
56
- New backend for TrackingFromFields (critical point based)
67
- Fast planar Rips filtration persistence computation
78
- Migration to ParaView 6

core/base/dimensionReduction/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ ttk_add_base_library(dimensionReduction
66
DEPENDS
77
triangulation
88
topoMap
9+
topologicalDimensionReduction
910
)
1011

1112
install(

core/base/dimensionReduction/DimensionReduction.cpp

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
#include <DimensionReduction.h>
22
#include <TopoMap.h>
33

4-
#include <map>
5-
64
#define VALUE_TO_STRING(x) #x
75
#define VALUE(x) VALUE_TO_STRING(x)
86

@@ -77,6 +75,40 @@ int DimensionReduction::execute(
7775
return 0;
7876
}
7977

78+
if(this->Method == METHOD::AE) {
79+
#ifdef TTK_ENABLE_TORCH
80+
TopologicalDimensionReduction tcdr(
81+
ae_CUDA, ae_Deterministic, ae_Seed, NumberOfComponents, ae_Epochs,
82+
ae_LearningRate, ae_Optimizer, ae_Method, ae_Model, ae_Architecture,
83+
ae_Activation, ae_BatchSize, ae_BatchNormalization, ae_RegCoefficient,
84+
IsInputImages);
85+
tcdr.setDebugLevel(debugLevel_);
86+
tcdr.setThreadNumber(threadNumber_);
87+
88+
outputEmbedding.resize(NumberOfComponents);
89+
for(int d = 0; d < NumberOfComponents; d++)
90+
outputEmbedding[d].resize(nRows);
91+
92+
if(ae_PreOptimize) {
93+
DimensionReduction initDR;
94+
initDR.setDebugLevel(debugLevel_);
95+
initDR.setInputMethod(ae_PreOptimizeMethod);
96+
std::vector<std::vector<double>> latentInitialization;
97+
initDR.execute(latentInitialization, inputMatrix, nRows, nColumns);
98+
tcdr.setLatentInitialization(latentInitialization);
99+
}
100+
101+
tcdr.execute(outputEmbedding, inputMatrix, nRows);
102+
103+
this->printMsg("Computed AE dimension reduction", 1.0, t.getElapsedTime(),
104+
threadNumber_);
105+
return 0;
106+
#else
107+
this->printErr("Unavailable backend: Torch is required.");
108+
return 1;
109+
#endif
110+
}
111+
80112
#ifdef TTK_ENABLE_SCIKIT_LEARN
81113
#ifndef TTK_ENABLE_KAMIKAZE
82114
if(majorVersion_ < '3')

core/base/dimensionReduction/DimensionReduction.h

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@
4040
/// href="https://topology-tool-kit.github.io/examples/persistentGenerators_periodicPicture/">Persistent
4141
/// Generators Periodic Picture example</a> \n
4242
/// - <a
43+
/// href="https://topology-tool-kit.github.io/examples/topoAEppTeaser/">Topological
44+
/// Autoencoders++ Teaser example</a> \n
45+
/// - <a
4346
/// href="https://topology-tool-kit.github.io/examples/topoMapTeaser/">TopoMap
4447
/// Teaser example</a> \n
4548
///
@@ -48,12 +51,29 @@
4851
/// "Topomap: A 0-dimensional homology preserving projection of high-dimensional
4952
/// data"\n Harish Doraiswamy, Julien Tierny, Paulo J. S. Silva, Luis Gustavo
5053
/// Nonato, and Claudio Silva\n Proc. of IEEE VIS 2020.\n IEEE Transactions on
51-
/// Visualization and Computer Graphics 27(2): 561-571, 2020.
54+
/// Visualization and Computer Graphics 27(2): 561-571, 2020. \n
55+
///
56+
/// "Topological Autoencoders" \n
57+
/// Michael Moor, Max Horn, Bastian Rieck, Karsten Borgwardt, \n
58+
/// Proceedings of the 37th International Conference on Machine Learning,
59+
/// 2020. \n
60+
///
61+
/// "Optimizing persistent homology-based functions" \n
62+
/// Mathieu Carriere, Frederic Chazal, Marc Glisse, Yuichi Ike,
63+
/// Hariprasad Kannan, Yuhei Umeda, \n
64+
/// Proceedings of the 38th International Conference on Machine Learning,
65+
/// 2021. \n
66+
///
67+
/// "Topological Autoencoders++: Fast and Accurate Cycle-Aware Dimensionality
68+
/// Reduction" \n
69+
/// Mattéo Clémot, Julie Digne, Julien Tierny, \n
70+
/// arXiv preprint, 2025.
5271

5372
#pragma once
5473

5574
#include <Debug.h>
5675
#include <TopoMap.h>
76+
#include <TopologicalDimensionReduction.h>
5777

5878
namespace ttk {
5979

@@ -78,6 +98,8 @@ namespace ttk {
7898
PCA = 5,
7999
/** TopoMap */
80100
TOPOMAP = 6,
101+
/** AutoEncoder */
102+
AE = 7,
81103
};
82104

83105
inline void setSEParameters(const std::string &Affinity,
@@ -228,6 +250,9 @@ namespace ttk {
228250
case METHOD::TOPOMAP:
229251
methodName = "TopoMap (IEEE VIS 2020)";
230252
break;
253+
case METHOD::AE:
254+
methodName = "Autoencoder";
255+
break;
231256
}
232257
this->printMsg("Using backend `" + methodName + "`");
233258
}
@@ -322,6 +347,26 @@ namespace ttk {
322347
bool topomap_CheckMST;
323348
TopoMap::STRATEGY topomap_Strategy{TopoMap::STRATEGY::KRUSKAL};
324349

350+
// AutoEncoder
351+
bool ae_CUDA{true};
352+
bool ae_Deterministic{false};
353+
int ae_Seed{0};
354+
int ae_Epochs{1000};
355+
double ae_LearningRate{1e-2};
356+
TopologicalDimensionReduction::OPTIMIZER ae_Optimizer{
357+
TopologicalDimensionReduction::OPTIMIZER::ADAM};
358+
TopologicalDimensionReduction::REGUL ae_Method{
359+
TopologicalDimensionReduction::REGUL::ASYMMETRIC_CASCADE};
360+
TopologicalDimensionReduction::MODEL ae_Model{
361+
TopologicalDimensionReduction::MODEL::AUTOENCODER};
362+
std::string ae_Architecture{"32 32"};
363+
std::string ae_Activation{"ReLU"};
364+
int ae_BatchSize{0};
365+
bool ae_BatchNormalization{true};
366+
double ae_RegCoefficient{1e-2};
367+
bool ae_PreOptimize{false};
368+
METHOD ae_PreOptimizeMethod{METHOD::PCA};
369+
325370
// testing
326371
std::string ModulePath{"default"};
327372
std::string ModuleName{"dimensionReduction"};
@@ -334,5 +379,6 @@ namespace ttk {
334379
int IsDeterministic{true};
335380
char majorVersion_{'0'};
336381
bool IsInputADistanceMatrix{false};
382+
bool IsInputImages{false};
337383
};
338384
} // namespace ttk

core/base/discreteMorseSandwichMPI/DiscreteMorseSandwichMPI.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
/// "Distributed Discrete Morse Sandwich: Efficient Computation
1313
// of Persistence Diagrams for Massive Scalar Data" \n
1414
/// Eve Le Guillou, Pierre Fortin, Julien Tierny \n
15+
/// IEEE Transactions on Parallel and Distributed Systems, 2025. \n
1516
/// arXiv:2505.21266, 2025.
1617
///
1718
///

core/base/persistenceDiagram/PersistenceDiagram.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,8 @@
6464
/// "Distributed Discrete Morse Sandwich: Efficient Computation of Persistence
6565
/// Diagrams for Massive Scalar Data" \n
6666
/// Eve Le Guillou, Pierre Fortin, Julien Tierny \n
67-
/// https://arxiv.org/abs/2505.21266, 2025.
67+
/// IEEE Transactions on Parallel and Distributed Systems, 2025. \n
68+
/// https://arxiv.org/abs/2505.21266, 2025. \n
6869
/// Fast, hybrid MPI-OpenMP backend for large-scale datasets on supercomputers.
6970
///
7071
/// \sa ttkPersistenceDiagram.cpp %for a usage example.

core/base/ripsPersistenceDiagram/RipsPersistenceDiagramUtils.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace ttk::rpd {
1414
using value_t = double;
1515
constexpr value_t inf = std::numeric_limits<value_t>::infinity();
1616

17-
using PointCloud = std::vector<std::vector<double>>;
17+
using PointCloud = std::vector<std::vector<value_t>>;
1818

1919
using Simplex = std::vector<id_t>;
2020
using FiltratedSimplex = std::pair<Simplex, value_t>;
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
ttk_add_base_library(topologicalDimensionReduction
2+
SOURCES
3+
DimensionReductionModel.cpp
4+
TopologicalLoss.cpp
5+
TopologicalDimensionReduction.cpp
6+
HEADERS
7+
DimensionReductionModel.h
8+
TopologicalLoss.h
9+
TopologicalDimensionReduction.h
10+
DEPENDS
11+
persistenceDiagramWarmRestartAuction
12+
ripsPersistenceDiagram)
13+
14+
if(TTK_ENABLE_TORCH)
15+
target_link_libraries(topologicalDimensionReduction PUBLIC "${TORCH_LIBRARIES}")
16+
target_include_directories(topologicalDimensionReduction PUBLIC ${TORCH_INCLUDE_DIRS})
17+
target_compile_definitions(topologicalDimensionReduction PUBLIC TTK_ENABLE_TORCH)
18+
endif()
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
#include "DimensionReductionModel.h"
2+
#include <regex>
3+
4+
#ifdef TTK_ENABLE_TORCH
5+
6+
ttk::AutoEncoder::AutoEncoder(int inputDim,
7+
int latentDim,
8+
const std::string &layersDescription,
9+
const std::string &activation,
10+
bool useBN) {
11+
12+
std::istringstream iss(layersDescription);
13+
const std::vector<std::string> hiddenDimsParsed(
14+
std::istream_iterator<std::string>{iss},
15+
std::istream_iterator<std::string>());
16+
std::vector<unsigned> dims(1, inputDim);
17+
for(const std::string &s : hiddenDimsParsed)
18+
dims.push_back(std::stoi(s));
19+
dims.push_back(latentDim);
20+
21+
const int n = dims.size() - 1;
22+
encoder = torch::nn::Sequential(torch::nn::Linear(dims[0], dims[1]));
23+
decoder = torch::nn::Sequential(torch::nn::Linear(dims[n], dims[n - 1]));
24+
for(unsigned i = 1; i < dims.size() - 1; ++i) {
25+
if(activation == "ReLU") {
26+
encoder->push_back(torch::nn::ReLU());
27+
decoder->push_back(torch::nn::ReLU());
28+
} else if(activation == "Tanh") {
29+
encoder->push_back(torch::nn::Tanh());
30+
decoder->push_back(torch::nn::Tanh());
31+
}
32+
if(useBN) {
33+
encoder->push_back(torch::nn::BatchNorm1d(dims[i]));
34+
decoder->push_back(torch::nn::BatchNorm1d(dims[n - i]));
35+
}
36+
encoder->push_back(torch::nn::Linear(dims[i], dims[i + 1]));
37+
decoder->push_back(torch::nn::Linear(dims[n - i], dims[n - (i + 1)]));
38+
}
39+
register_module("encoder", encoder);
40+
register_module("decoder", decoder);
41+
}
42+
43+
bool ttk::AutoEncoder::isStringValid(const std::string &s) {
44+
return std::regex_match(s, std::regex("([0-9]+( )*)*"));
45+
}
46+
47+
ttk::AutoDecoder::AutoDecoder(int inputDim,
48+
int inputSize,
49+
int latentDim,
50+
const std::string &layersDescription,
51+
const std::string &activation,
52+
bool useBN) {
53+
54+
std::istringstream iss(layersDescription);
55+
const std::vector<std::string> hiddenDimsParsed(
56+
std::istream_iterator<std::string>{iss},
57+
std::istream_iterator<std::string>());
58+
std::vector<unsigned> dims(1, inputDim);
59+
for(const std::string &s : hiddenDimsParsed)
60+
dims.push_back(std::stoi(s));
61+
dims.push_back(latentDim);
62+
63+
latent = torch::rand({inputSize, latentDim});
64+
65+
const int n = dims.size() - 1;
66+
decoder = torch::nn::Sequential(torch::nn::Linear(dims[n], dims[n - 1]));
67+
for(unsigned i = 1; i < dims.size() - 1; ++i) {
68+
if(activation == "ReLU")
69+
decoder->push_back(torch::nn::ReLU());
70+
else if(activation == "Tanh")
71+
decoder->push_back(torch::nn::Tanh());
72+
if(useBN)
73+
decoder->push_back(torch::nn::BatchNorm1d(dims[n - i]));
74+
decoder->push_back(torch::nn::Linear(dims[n - i], dims[n - (i + 1)]));
75+
}
76+
register_parameter("latent", latent, true);
77+
register_module("decoder", decoder);
78+
}
79+
80+
ttk::DirectOptimization::DirectOptimization(int inputSize, int latentDim) {
81+
latent = torch::rand({inputSize, latentDim});
82+
register_parameter("latent", latent, true);
83+
}
84+
85+
ttk::ConvolutionalAutoEncoder::ConvolutionalAutoEncoder(
86+
int imageSide,
87+
int latentDim,
88+
const std::string &layersDescription,
89+
bool useBN) {
90+
std::istringstream iss(layersDescription);
91+
const std::vector<std::string> hiddenDimsParsed(
92+
std::istream_iterator<std::string>{iss},
93+
std::istream_iterator<std::string>());
94+
std::vector<int> denseLayersSizes = {-1};
95+
std::vector<int> convolutionalLayersChannels = {1};
96+
std::vector<int> convolutionalLayersStrides;
97+
for(const std::string &s : hiddenDimsParsed) {
98+
if(std::regex_match(s, std::regex("c[0-9]+/[0-9]+"))) {
99+
convolutionalLayersChannels.push_back(
100+
std::stoi(s.substr(1, s.find('/'))));
101+
convolutionalLayersStrides.push_back(
102+
std::stoi(s.substr(s.find('/') + 1, s.length())));
103+
} else
104+
denseLayersSizes.push_back(std::stoi(s));
105+
}
106+
denseLayersSizes.push_back(latentDim);
107+
int convolutionalOutputImageSide = imageSide;
108+
109+
/*** convolutional encoder ***/
110+
/** unflattening for convolutional layers **/
111+
encoder->push_back(torch::nn::Unflatten(
112+
torch::nn::UnflattenOptions(1, {1, imageSide, imageSide})));
113+
/** convolutional layers **/
114+
for(unsigned c = 0; c < convolutionalLayersChannels.size() - 1; ++c) {
115+
encoder->push_back(torch::nn::Conv2d(
116+
torch::nn::Conv2dOptions(
117+
convolutionalLayersChannels[c], convolutionalLayersChannels[c + 1], 3)
118+
.padding(1)
119+
.stride(convolutionalLayersStrides[c])));
120+
encoder->push_back(torch::nn::ReLU());
121+
convolutionalOutputImageSide /= convolutionalLayersStrides[c];
122+
}
123+
/** flattening for dense layers **/
124+
encoder->push_back(torch::nn::Flatten());
125+
126+
/*** dense encoder / decoder ***/
127+
denseLayersSizes[0]
128+
= convolutionalLayersChannels[convolutionalLayersChannels.size() - 1]
129+
* convolutionalOutputImageSide * convolutionalOutputImageSide;
130+
const int n = denseLayersSizes.size() - 1;
131+
encoder->push_back(
132+
torch::nn::Linear(denseLayersSizes[0], denseLayersSizes[1]));
133+
decoder->push_back(
134+
torch::nn::Linear(denseLayersSizes[n], denseLayersSizes[n - 1]));
135+
for(unsigned i = 1; i < denseLayersSizes.size() - 1; ++i) {
136+
encoder->push_back(torch::nn::ReLU());
137+
decoder->push_back(torch::nn::ReLU());
138+
if(useBN) {
139+
encoder->push_back(torch::nn::BatchNorm1d(denseLayersSizes[i]));
140+
decoder->push_back(torch::nn::BatchNorm1d(denseLayersSizes[n - i]));
141+
}
142+
encoder->push_back(
143+
torch::nn::Linear(denseLayersSizes[i], denseLayersSizes[i + 1]));
144+
decoder->push_back(torch::nn::Linear(
145+
denseLayersSizes[n - i], denseLayersSizes[n - (i + 1)]));
146+
}
147+
148+
/*** convolutional decoder ***/
149+
/** unflattening for convolutional layers **/
150+
decoder->push_back(torch::nn::Unflatten(torch::nn::UnflattenOptions(
151+
1, {convolutionalLayersChannels[convolutionalLayersChannels.size() - 1],
152+
convolutionalOutputImageSide, convolutionalOutputImageSide})));
153+
/** convolutional layers **/
154+
for(unsigned c = convolutionalLayersChannels.size() - 1; c > 0; --c) {
155+
decoder->push_back(torch::nn::ReLU());
156+
decoder->push_back(torch::nn::ConvTranspose2d(
157+
torch::nn::ConvTranspose2dOptions(
158+
convolutionalLayersChannels[c], convolutionalLayersChannels[c - 1], 3)
159+
.padding(1)
160+
.stride(convolutionalLayersStrides[c - 1])
161+
.output_padding(1)));
162+
}
163+
/** flattening for output **/
164+
decoder->push_back(torch::nn::Flatten());
165+
166+
register_module("encoder", encoder);
167+
register_module("decoder", decoder);
168+
}
169+
170+
bool ttk::ConvolutionalAutoEncoder::isStringValid(const std::string &s) {
171+
return std::regex_match(s, std::regex("(c[0-9]+/[0-9]+( )*)*([0-9]+( )*)*"));
172+
}
173+
174+
#endif

0 commit comments

Comments
 (0)