Differentiable geodesics
geodesic and transport_cost take torch tensors and return tensors. With
shooting, gradients flow back to both endpoints through W2, the density path,
the momenta and the potentials.
import torch
import graphtransport as gt
G = gt.MarkovGraph(*gt.grid_markov_chain(5))
pi = torch.tensor(G.pi)
xy = torch.tensor([(i % 5, i // 5) for i in range(G.n)], dtype=torch.float64)
target = torch.exp(-((xy - 4) ** 2).sum(1) / 2) + 0.05 # a bump in the corner
target = target / (target @ pi)
logits = torch.zeros(G.n, dtype=torch.float64, requires_grad=True)
optimizer = torch.optim.Adam([logits], lr=0.3)
for step in range(30):
optimizer.zero_grad()
W = gt.transport_cost(G, torch.softmax(logits, 0) / pi, target)
W.backward()
optimizer.step()
The gradients are exact for the discrete problem the solver solves. They come from the implicit function theorem at the solution, not from differentiating through Newton's iterations, so they agree with finite differences of the returned values.
- Gradients are first order: differentiating a gradient again
(
create_graph=True) raises. - Where
rhoA == rhoB,transport_cost's gradient is 0, the subgradient at its minimum, as fortorch.linalg.norm. - The SOCP and Sinkhorn methods are not differentiable here. With inputs that require grad they raise, and so does shooting where it would otherwise fall back to the SOCP.
- Tensors are computed in float64 on the CPU.
barycenterandanalysisdo not take tensors yet.