Skip to content

Commit 3dbda88

Browse files
author
Boudjema Ali
committed
doc
1 parent 3f6183b commit 3dbda88

3 files changed

Lines changed: 63 additions & 140 deletions

File tree

ot/lp/__init__.py

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,11 @@
3838

3939
from .solver_tree import (
4040
topological_sort,
41-
tree_wasserstein,
41+
tree_wasserstein_distance,
4242
)
4343

4444
from .tree_barycenter import (
45-
tree_barycenter,
4645
fixed_support_tree_barycenter,
47-
sliced_fixed_support_tree_barycenter,
4846
)
4947

5048
__all__ = [
@@ -72,8 +70,6 @@
7270
"NorthWestMMGluing",
7371
"ot_barycenter_energy",
7472
"topological_sort",
75-
"tree_wasserstein",
76-
"tree_barycenter",
73+
"tree_wasserstein_distance",
7774
"fixed_support_tree_barycenter",
78-
"sliced_fixed_support_tree_barycenter",
7975
]

ot/lp/solver_tree.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def topological_sort(tree):
5050
return np.array(topo_order)
5151

5252

53-
def tree_wasserstein(
53+
def tree_wasserstein_distance(
5454
tree, length, u_weights, v_weights, topo_order=None, return_plans=False
5555
):
5656
r"""
@@ -59,9 +59,9 @@ def tree_wasserstein(
5959
Parameters
6060
----------
6161
tree : array_like, shape(n)
62-
ancestor of each node in the tree (ancestor of root is root)
62+
parent of each node in the tree (parent of root is root)
6363
length : array_like, shape(n)
64-
length of the arc above each node (length of root is 0)
64+
length of the edge above each node (length of root is 0)
6565
u_weights : array_like, shape(n)
6666
weights of the first empirical distributions
6767
v_weights : array_like, shape(n)

ot/lp/tree_barycenter.py

Lines changed: 58 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from ..backend import get_backend
22
import numpy as np
3-
from .solver_tree import topological_sort
43
from ..utils import proj_simplex
54
from ..utils import list_to_array
65

@@ -9,101 +8,6 @@
98
# IMPORTANT : ON PREND COMME CONVENTION QUE LES FEUILLES SONT LES PREMIERS SOMMETS DE L'ARBRE
109

1110

12-
def wgm(values, weights):
13-
# Returns the weighted geometric median
14-
15-
nx = get_backend(values, weights)
16-
17-
sorted_indices = np.argsort(values, kind="stable")
18-
19-
values_sorted = values[sorted_indices]
20-
weights_sorted = weights[sorted_indices]
21-
22-
cum_weights = nx.cumsum(weights_sorted)
23-
24-
id = nx.searchsorted(cum_weights, 0.5 - 1e9)
25-
26-
return values_sorted[id]
27-
28-
29-
def get_measure(z, tree, length):
30-
# Retrieves the measure from a vector after the wgm
31-
32-
n = z.shape[0]
33-
34-
nx = get_backend(length)
35-
36-
measure = nx.zeros(n)
37-
38-
for i in range(n):
39-
p = tree[i]
40-
41-
if i == p:
42-
measure[i] += 1
43-
else:
44-
measure[i] += z[i] / length[i]
45-
measure[p] -= z[i] / length[i]
46-
47-
return measure
48-
49-
50-
def tree_barycenter(tree, length, measure, weights, topo_order=None):
51-
r"""
52-
Computes the tree wasserstein barycenter for a given tree between multiplie empirical distributions
53-
54-
Parameters
55-
----------
56-
tree : array_like, shape(n)
57-
ancestor of each node in the tree (ancestor of root is root)
58-
length : array_like, shape(n)
59-
length of the arc above each node (length of root is 0)
60-
measure : array_like, shape(m, n)
61-
distributions in the tree
62-
weights : array_like, shape(m)
63-
weight of each distribution
64-
65-
Returns
66-
-------
67-
barycenter : array_like, shape(n)
68-
distribution of the barycenter
69-
70-
Reference
71-
---------
72-
The code is a direct implementation of the algorithm described in
73-
Tree-Wasserstein Barycenter for Large-Scale Multilevel Clustering and Scalable Bayes
74-
75-
"""
76-
n_measure = measure.shape[0]
77-
n_node = tree.shape[0]
78-
79-
assert n_measure == weights.shape[0], "dimension error"
80-
81-
nx = get_backend(measure, weights, length)
82-
83-
z_measure = nx.zeros((n_measure, n_node))
84-
85-
if topo_order is None:
86-
topo_order = topological_sort(tree)
87-
88-
for cur_node in topo_order:
89-
p = tree[cur_node]
90-
91-
for id_mes in range(n_measure):
92-
z_measure[id_mes][cur_node] += measure[id_mes][cur_node]
93-
94-
if cur_node != p:
95-
z_measure[id_mes][p] += z_measure[id_mes][cur_node]
96-
97-
z = nx.zeros(n_node)
98-
99-
for cur_node in range(n_node):
100-
z_measure[:, cur_node] *= length[cur_node]
101-
102-
z[cur_node] = wgm(z_measure[:, cur_node], weights)
103-
104-
return get_measure(z, tree, length)
105-
106-
10711
def get_B_matrix(tree, length, nb_leafs):
10812
nx = get_backend(length)
10913

@@ -141,38 +45,14 @@ def get_gradient(cur_B, B_mes_sorted, B, nb_mes, nb_nodes):
14145
return g
14246

14347

144-
def fixed_support_tree_barycenter(tree, length, measures, nb_itr=100, step=0.1):
145-
nx = get_backend(length, measures)
146-
nb_leafs = measures.shape[1]
147-
148-
B = get_B_matrix(tree, length, nb_leafs)
149-
150-
nb_mes = measures.shape[0]
151-
nb_nodes = tree.shape[0]
152-
153-
cur_mes = nx.ones(nb_leafs) / nb_leafs
154-
155-
B_mes = list_to_array([B.dot(measures[i]) for i in range(nb_mes)])
156-
157-
sigma = nx.argsort(B_mes, axis=0)
158-
159-
B_mes_sorted = nx.take_along_axis(B_mes, sigma, axis=0)
160-
161-
for itr in range(nb_itr):
162-
cur_B = B.dot(cur_mes)
163-
164-
g = get_gradient(cur_B, B_mes_sorted, B, nb_mes, nb_nodes)
165-
166-
cur_mes -= step * g
167-
168-
cur_mes = proj_simplex(cur_mes)
169-
170-
return cur_mes
171-
172-
17348
def pre_process_trees(tree_list, length_list, measures):
17449
nx = get_backend(length_list, measures)
17550

51+
if tree_list.ndim == 1:
52+
tree_list = nx.reshape(tree_list, (1, *tree_list.shape))
53+
length_list = nx.reshape(length_list, (1, *length_list.shape))
54+
measures = nx.reshape(measures, (1, *measures.shape))
55+
17656
nb_leafs = measures.shape[2]
17757

17858
prepared_trees = []
@@ -197,22 +77,69 @@ def pre_process_trees(tree_list, length_list, measures):
19777
return prepared_trees
19878

19979

200-
def sliced_fixed_support_tree_barycenter(
201-
tree_list, length_list, measures, nb_itr=100, step=0.01, tol=1e-5
80+
def fixed_support_tree_barycenter(
81+
tree_list, length_list, measures, nb_itr=100, step=0.01, tol=1e-5, init_measure=None
20282
):
20383
"""
84+
Computes the Tree-Wasserstein (or Tree-Sliced) barycenter for one or multiple trees,
85+
with the constraint that the support of the barycenter is fixed at the leaves.
86+
It is assumed that the leaves correspond to the first nodes of the tree (indices 0 to k-1).
87+
While the number of leaves (k) must be strictly identical across all structures to ensure
88+
consistent alignment, the total number of nodes (leaves + internal nodes) can
89+
freely vary from one tree to another.
90+
91+
If a single tree structure is provided (e.g., 1D arrays for tree_list/length_list
92+
and 2D for measures), the function automatically expands their dimensions to 3D
93+
internal structures to handle them uniformly as a multi-tree setting with t=1.
94+
20495
Parameters
20596
-----------
206-
tree_list : array_like, shape (t, n)
207-
length_list : array_like, shape (t, n)
208-
measures : array_like, shape (t, m, k)
97+
tree_list : array_like
98+
A single tree of shape (n_t,) or a list of t trees where each tree has shape (n_t,).
99+
n_t is the total number of nodes in that specific tree. tree[i] contains the index
100+
of the parent of node i (with tree[root] == root).
101+
length_list : array_like
102+
The edge weights corresponding to tree_list. A single array of shape (n_t,) or a list
103+
of t arrays, where the t-th array has shape (n_t,) and contains the length of the edge
104+
connecting node i to its parent.
105+
measures : array_like, shape (t, m, k) or (m,k)
106+
The input probability distributions mapped to the leaves.
107+
k is the fixed number of leaves shared by all trees, and m is the number
108+
of measures. Accepts a 2D array of shape (m, k) for a single tree, or a 3D array
109+
of shape (t, m, k) in a multi-tree setting.
110+
nb_tr : int, optional
111+
the maximal number of iterations for the subgradient descent
112+
step : float, optional
113+
the step size of the descent
114+
tol : float, optional
115+
Convergence tolerance. The descent stops if the L2 norm of the difference
116+
between two consecutive iterations is smaller than tol.
117+
init_measure : array_like, shape (k), optional
118+
The starting point of the descent, default is None
119+
120+
Returns
121+
-------
122+
cur_mes : array_like, shape (k)
123+
The computed fixed-support barycenter supported on the k leaves.
124+
125+
References
126+
----------
127+
"Fixed Support Tree-Sliced Wasserstein Barycenter"
209128
"""
210129

211130
nx = get_backend(length_list, measures)
212131

132+
assert (
133+
tree_list.shape[0] == length_list.shape[0] == measures.shape[0]
134+
and tree_list.shape[1] == length_list.shape[1]
135+
), "dimension error in the input"
136+
213137
nb_leafs = measures.shape[2]
214138

215-
cur_mes = nx.ones(nb_leafs) / nb_leafs
139+
if init_measure is None:
140+
cur_mes = nx.ones(nb_leafs) / nb_leafs
141+
else:
142+
cur_mes = init_measure
216143

217144
prepared_trees = pre_process_trees(tree_list, length_list, measures)
218145

0 commit comments

Comments
 (0)