@@ -619,7 +619,7 @@ def solve_sample(
619619 verbose = False ,
620620 grad = "autodiff" ,
621621 random_state = None ,
622- debiased = False ,
622+ debias = False ,
623623):
624624 r"""Solve the discrete optimal transport problem using the samples in the source and target domains.
625625
@@ -714,7 +714,7 @@ def solve_sample(
714714 detached. This is useful for memory saving when only the value is needed.
715715 random_state : int, optional
716716 The random state for sampling the components in each distribution for method='nystroem'.
717- debiased : bool, optional
717+ debias : bool, optional
718718 Whether to use the debiased version of the OT problem, by default False
719719 if True, the value returned is the Sinkhorn divergence but the plan is
720720 still the Sinkhorn plan. The results for all pairs of problems and
@@ -975,7 +975,7 @@ def solve_sample(
975975
976976 """
977977
978- if debiased :
978+ if debias :
979979 dict_params = dict (
980980 metric = metric ,
981981 reg = reg ,
@@ -997,39 +997,106 @@ def solve_sample(
997997 verbose = verbose ,
998998 grad = grad ,
999999 random_state = random_state ,
1000- debiased = False ,
1000+ debias = False ,
10011001 )
10021002
1003- if isinstance (debiased , str ) and debiased .lower () == "split" :
1004- raise NotImplementedError (
1005- "Debiased OT with split is not implemented yet. Please use debiased=True for now."
1003+ nx = get_backend (X_a , X_b , a , b )
1004+
1005+ if isinstance (debias , str ) and debias .lower () == "split" :
1006+ # split the samples into two halves with each half the mass
1007+
1008+ n_a = X_a .shape [0 ]
1009+ n_b = X_b .shape [0 ]
1010+
1011+ if a is None :
1012+ a = nx .ones (n_a , type_as = X_a ) / n_a
1013+ if b is None :
1014+ b = nx .ones (n_b , type_as = X_b ) / n_b
1015+
1016+ # find the split indices
1017+ acs = nx .cumsum (a )
1018+ bcs = nx .cumsum (b )
1019+
1020+ thr_a = 0.5 * nx .sum (a )
1021+ thr_b = 0.5 * nx .sum (b )
1022+
1023+ idx_a = nx .searchsorted (acs , thr_a )
1024+ idx_b = nx .searchsorted (bcs , thr_b )
1025+
1026+ # split the samples and weights
1027+ X_a1 , X_a2 = X_a [: idx_a + 1 ], X_a [idx_a :]
1028+ X_b1 , X_b2 = X_b [: idx_b + 1 ], X_b [idx_b :]
1029+
1030+ # compute weights for each half and adjust the last/first weight to sum to 0.5
1031+ a1 , a2 = a [: idx_a + 1 ], a [idx_a :]
1032+ a1 [- 1 ] = a1 [- 1 ] * nx .detach ((thr_a - nx .sum (a1 [:- 1 ])) / a1 [- 1 ])
1033+ a2 [0 ] = a2 [0 ] * nx .detach ((thr_a - nx .sum (a2 [1 :])) / a2 [0 ])
1034+ b1 , b2 = b [: idx_b + 1 ], b [idx_b :]
1035+ b1 [- 1 ] = b1 [- 1 ] * nx .detach ((thr_b - nx .sum (b1 [:- 1 ])) / b1 [- 1 ])
1036+ b2 [0 ] = b2 [0 ] * nx .detach ((thr_b - nx .sum (b2 [1 :])) / b2 [0 ])
1037+
1038+ # compute the four OT problems
1039+ resaa = solve_sample (X_a1 , X_a2 , a = a1 , b = a2 , ** dict_params )
1040+ resbb = solve_sample (X_b1 , X_b2 , a = b1 , b = b2 , ** dict_params )
1041+ resab1 = solve_sample (X_a1 , X_b1 , a = a1 , b = b1 , ** dict_params )
1042+ resab2 = solve_sample (X_a2 , X_b2 , a = a2 , b = b2 , ** dict_params )
1043+
1044+ # compute debiased values
1045+ value = 0.5 * (resab1 .value + resab2 .value ) - 0.5 * (
1046+ resaa .value + resbb .value
10061047 )
1048+ value_linear = 0.5 * (resab1 .value_linear + resab2 .value_linear ) - 0.5 * (
1049+ resaa .value_linear + resbb .value_linear
1050+ )
1051+
1052+ log = {
1053+ "res_aa" : resaa ,
1054+ "res_bb" : resbb ,
1055+ "res_ab1" : resab1 ,
1056+ "res_ab2" : resab2 ,
1057+ }
1058+
1059+ res = OTResult (
1060+ value = value ,
1061+ value_linear = value_linear ,
1062+ plan = None , # no plan for debiased version
1063+ potentials = None , # no potentials for debiased version
1064+ sparse_plan = None , # no sparse plan for debiased version
1065+ lazy_plan = None , # no lazy plan for debiased version
1066+ status = "Debiased" ,
1067+ log = log ,
1068+ backend = nx ,
1069+ )
1070+
10071071 else :
10081072 # standard debiasing à la sinkhorn divergence
10091073
1010- res11 = solve_sample (X_a , X_a , a = a , b = a , ** dict_params )
1074+ resaa = solve_sample (X_a , X_a , a = a , b = a , ** dict_params )
10111075
1012- res22 = solve_sample (X_b , X_b , a = b , b = b , ** dict_params )
1076+ resbb = solve_sample (X_b , X_b , a = b , b = b , ** dict_params )
10131077
1014- res12 = solve_sample (X_a , X_b , a = a , b = b , ** dict_params )
1015- value = res12 .value - 0.5 * (res11 .value + res22 .value )
1016- value_linear = res12 .value_linear - 0.5 * (
1017- res11 .value_linear + res22 .value_linear
1078+ resab = solve_sample (X_a , X_b , a = a , b = b , ** dict_params )
1079+
1080+ # compute debiased values
1081+ value = resab .value - 0.5 * (resaa .value + resbb .value )
1082+ value_linear = resab .value_linear - 0.5 * (
1083+ resaa .value_linear + resbb .value_linear
10181084 )
10191085
1020- log = {"res11 " : res11 , "res22 " : res22 , "res12 " : res12 }
1086+ log = {"res_aa " : resaa , "res_bb " : resbb , "res_ab " : resab }
10211087
10221088 res = OTResult (
10231089 value = value ,
10241090 value_linear = value_linear ,
1025- plan = res12 .plan ,
1026- potentials = res12 .potentials ,
1027- sparse_plan = res12 .sparse_plan ,
1028- lazy_plan = res12 .lazy_plan ,
1029- status = res12 .status ,
1091+ plan = resab .plan ,
1092+ potentials = resab .potentials ,
1093+ sparse_plan = resab .sparse_plan ,
1094+ lazy_plan = resab .lazy_plan ,
1095+ status = resab .status ,
10301096 log = log ,
1031- backend = res12 . backend ,
1097+ backend = nx ,
10321098 )
1099+
10331100 # return debiased result
10341101 return res
10351102
0 commit comments