[{"data":1,"prerenderedAt":2330},["ShallowReactive",2],{"navigation":3,"index":174},[4,8,101,165,170],{"title":5,"path":6,"stem":7},"Getting started","\u002Fgetting-started","1.getting-started",{"title":9,"path":10,"stem":11,"children":12,"page":-1},"Algorithms","\u002Falgorithms","2.algorithms",[13,15,58],{"title":9,"path":10,"stem":14},"2.algorithms\u002Findex",{"title":16,"path":17,"stem":18,"children":19,"page":-1},"Assignment","\u002Falgorithms\u002Fassignment","2.algorithms\u002F1.assignment\u002Findex",[20,21,25,29,32,36,40],{"title":16,"path":17,"stem":18},{"title":22,"path":23,"stem":24},"Quickstart","\u002Falgorithms\u002Fassignment\u002Fquickstart","2.algorithms\u002F1.assignment\u002F1.quickstart",{"title":26,"path":27,"stem":28},"Tracking","\u002Falgorithms\u002Fassignment\u002Ftracking","2.algorithms\u002F1.assignment\u002F2.tracking",{"title":9,"path":30,"stem":31},"\u002Falgorithms\u002Fassignment\u002Falgorithms","2.algorithms\u002F1.assignment\u002F3.algorithms",{"title":33,"path":34,"stem":35},"Reference","\u002Falgorithms\u002Fassignment\u002Freference","2.algorithms\u002F1.assignment\u002F4.reference",{"title":37,"path":38,"stem":39},"Choosing","\u002Falgorithms\u002Fassignment\u002Fchoosing","2.algorithms\u002F1.assignment\u002F5.choosing",{"title":41,"path":42,"stem":43,"children":44,"page":-1},"Tutorials","\u002Falgorithms\u002Fassignment\u002Ftutorials","2.algorithms\u002F1.assignment\u002F6.tutorials\u002Findex",[45,46,50,54],{"title":41,"path":42,"stem":43},{"title":47,"path":48,"stem":49},"Fundamentals","\u002Falgorithms\u002Fassignment\u002Ftutorials\u002Ffundamentals","2.algorithms\u002F1.assignment\u002F6.tutorials\u002F1.fundamentals",{"title":51,"path":52,"stem":53},"Backends","\u002Falgorithms\u002Fassignment\u002Ftutorials\u002Fbackends","2.algorithms\u002F1.assignment\u002F6.tutorials\u002F2.backends",{"title":55,"path":56,"stem":57},"Object tracking","\u002Falgorithms\u002Fassignment\u002Ftutorials\u002Ftracking","2.algorithms\u002F1.assignment\u002F6.tutorials\u002F3.tracking",{"title":59,"path":60,"stem":61,"children":62,"page":-1},"Transport","\u002Falgorithms\u002Ftransport","2.algorithms\u002F2.transport\u002Findex",[63,64,67,71,74,77,80,84],{"title":59,"path":60,"stem":61},{"title":22,"path":65,"stem":66},"\u002Falgorithms\u002Ftransport\u002Fquickstart","2.algorithms\u002F2.transport\u002F1.quickstart",{"title":68,"path":69,"stem":70},"Point-cloud tutorial","\u002Falgorithms\u002Ftransport\u002Fpoint-clouds","2.algorithms\u002F2.transport\u002F2.point-clouds",{"title":9,"path":72,"stem":73},"\u002Falgorithms\u002Ftransport\u002Falgorithms","2.algorithms\u002F2.transport\u002F3.algorithms",{"title":33,"path":75,"stem":76},"\u002Falgorithms\u002Ftransport\u002Freference","2.algorithms\u002F2.transport\u002F4.reference",{"title":37,"path":78,"stem":79},"\u002Falgorithms\u002Ftransport\u002Fchoosing","2.algorithms\u002F2.transport\u002F5.choosing",{"title":81,"path":82,"stem":83},"Building","\u002Falgorithms\u002Ftransport\u002Fbuilding","2.algorithms\u002F2.transport\u002F6.building",{"title":41,"path":85,"stem":86,"children":87,"page":-1},"\u002Falgorithms\u002Ftransport\u002Ftutorials","2.algorithms\u002F2.transport\u002F7.tutorials\u002Findex",[88,89,93,97],{"title":41,"path":85,"stem":86},{"title":90,"path":91,"stem":92},"Optimal transport","\u002Falgorithms\u002Ftransport\u002Ftutorials\u002Foptimal-transport","2.algorithms\u002F2.transport\u002F7.tutorials\u002F1.optimal-transport",{"title":94,"path":95,"stem":96},"Sinkhorn","\u002Falgorithms\u002Ftransport\u002Ftutorials\u002Fsinkhorn","2.algorithms\u002F2.transport\u002F7.tutorials\u002F2.sinkhorn",{"title":98,"path":99,"stem":100},"Point clouds","\u002Falgorithms\u002Ftransport\u002Ftutorials\u002Fpoint-clouds","2.algorithms\u002F2.transport\u002F7.tutorials\u002F3.point-clouds",{"title":102,"path":103,"stem":104,"children":105,"page":-1},"Resources","\u002Fresources","3.resources",[106,108,147,151,155],{"title":102,"path":103,"stem":107},"3.resources\u002Findex",{"title":41,"path":109,"stem":110,"children":111,"page":-1},"\u002Fresources\u002Ftutorials","3.resources\u002F1.tutorials\u002Findex",[112,113,131],{"title":41,"path":109,"stem":110},{"title":16,"path":114,"stem":115,"children":116,"page":130},"\u002Fresources\u002Ftutorials\u002Fassignment","3.resources\u002F1.tutorials\u002Fassignment",[117,122,126],{"title":118,"path":119,"stem":120,"icon":121},"Tutorial 1 — The Assignment Problem","\u002Fresources\u002Ftutorials\u002Fassignment\u002F01_the_assignment_problem","3.resources\u002F1.tutorials\u002Fassignment\u002F01_the_assignment_problem","i-lucide-notebook",{"title":123,"path":124,"stem":125,"icon":121},"Tutorial 2 — Backends and Batching","\u002Fresources\u002Ftutorials\u002Fassignment\u002F02_backends_and_batching","3.resources\u002F1.tutorials\u002Fassignment\u002F02_backends_and_batching",{"title":127,"path":128,"stem":129,"icon":121},"Tutorial 3 — Object Tracking with the Assignment Problem","\u002Fresources\u002Ftutorials\u002Fassignment\u002F03_object_tracking","3.resources\u002F1.tutorials\u002Fassignment\u002F03_object_tracking",false,{"title":59,"path":132,"stem":133,"children":134,"page":130},"\u002Fresources\u002Ftutorials\u002Ftransport","3.resources\u002F1.tutorials\u002Ftransport",[135,139,143],{"title":136,"path":137,"stem":138,"icon":121},"Tutorial 1 — What Is Optimal Transport?","\u002Fresources\u002Ftutorials\u002Ftransport\u002F01_optimal_transport","3.resources\u002F1.tutorials\u002Ftransport\u002F01_optimal_transport",{"title":140,"path":141,"stem":142,"icon":121},"Tutorial 2 — The Sinkhorn Algorithm","\u002Fresources\u002Ftutorials\u002Ftransport\u002F02_sinkhorn_algorithm","3.resources\u002F1.tutorials\u002Ftransport\u002F02_sinkhorn_algorithm",{"title":144,"path":145,"stem":146,"icon":121},"Tutorial 3 — Point-Cloud OT and Shape Learning","\u002Fresources\u002Ftutorials\u002Ftransport\u002F03_point_clouds","3.resources\u002F1.tutorials\u002Ftransport\u002F03_point_clouds",{"title":148,"path":149,"stem":150},"Assignment applications","\u002Fresources\u002Fassignment-applications","3.resources\u002F2.assignment-applications",{"title":152,"path":153,"stem":154},"Transport applications","\u002Fresources\u002Ftransport-applications","3.resources\u002F3.transport-applications",{"title":156,"path":157,"stem":158,"children":159,"page":-1},"Benchmarks","\u002Fresources\u002Fbenchmarks","3.resources\u002F4.benchmarks\u002Findex",[160,161],{"title":156,"path":157,"stem":158},{"title":162,"path":163,"stem":164},"Contributing benchmarks","\u002Fresources\u002Fbenchmarks\u002Fcontributing","3.resources\u002F4.benchmarks\u002Fcontributing",{"title":166,"path":167,"stem":168,"icon":169},"API Reference","\u002Fapi","4.api","i-lucide-package",{"title":171,"path":172,"stem":173},"References","\u002Freferences","5.references",{"id":175,"title":176,"body":177,"description":176,"extension":2322,"meta":2323,"navigation":272,"path":2324,"seo":2325,"stem":2328,"__hash__":2329},"landing\u002Findex.md","",{"type":178,"value":179,"toc":2320},"minimark",[180,183,2281,2297,2310,2316],[181,182],"landing-hero",{},[184,185,189,192,539,753,1038,1350,1352,1640,1998],"landing-section",{"index":186,"subtitle":187,"title":188},"01","Assignment covers discrete one-to-one matching; transport covers continuous distributions and point clouds.","Use cases",[190,191],"landing-family-label",{"label":16},[193,194,198,229],"landing-use-case",{":related":195,"domain":55,"index":186,"systems":196,"title":197},"[{\"label\":\"Batched tracking tutorial\",\"to\":\"\u002Falgorithms\u002Fassignment\u002Ftracking\"},{\"label\":\"jonker_dense_batch reference\",\"to\":\"\u002Falgorithms\u002Fassignment\u002Freference\"}]","SORT · ByteTrack · BoT-SORT · DeepSORT","Detection-to-track association",[199,200,201,218],"template",{"v-slot:prose":176},[202,203,204,205,209,210,213,214,217],"p",{},"A tracker reads detections each frame and assigns each one to a track. Build the per-frame ",[206,207,208],"code",{},"(N_tracks × M_detections)"," cost matrix — typically ",[206,211,212],{},"1 − IoU"," (intersection-over-union overlap) between predicted and detected bounding boxes, optionally filtered by distance between box centers (centroid gating) or visual similarity (appearance distance) — stack frames into a batch, and ",[206,215,216],{},"jonker_dense_batch"," solves the whole batch in one CUDA launch.",[202,219,220,221,224,225,228],{},"The ",[206,222,223],{},"_unpacked"," variant returns ",[206,226,227],{},"(matches, unmatched_tracks, unmatched_dets, n_matched)"," directly — removing the per-frame Python loop that most codebases use to separate matched from unmatched indices after getting the raw assignment.",[199,230,231],{"v-slot:code":176},[232,233,236,417],"code-tabs",{"default":234,"direct-label":235},"match","jonker_dense_batch_unpacked",[199,237,238],{"v-slot:match":176},[239,240,244],"pre",{"className":241,"code":242,"language":243,"meta":176,"style":176},"language-python shiki shiki-themes material-theme-lighter github-light github-dark","import torch\nimport torchmatch\n\n# costs: (B, N_tracks, M_dets) of `1 - IoU`,\n# with +inf where centroid distance > gate.\ncosts = build_iou_cost_batch(\n    tracks, detections, gate=0.3,\n)                                          # (B, N, M)\n\nmatches, ur, uc, n_matched = torchmatch.assignment.solve(\n    costs, unpack=True,\n)\n# matches[b, :n_matched[b]] = (track_idx, det_idx) pairs\n","python",[206,245,246,259,267,274,281,287,305,332,341,346,386,405,411],{"__ignoreMap":176},[247,248,251,255],"span",{"class":249,"line":250},"line",1,[247,252,254],{"class":253},"sVHd0","import",[247,256,258],{"class":257},"su5hD"," torch\n",[247,260,262,264],{"class":249,"line":261},2,[247,263,254],{"class":253},[247,265,266],{"class":257}," torchmatch\n",[247,268,270],{"class":249,"line":269},3,[247,271,273],{"emptyLinePlaceholder":272},true,"\n",[247,275,277],{"class":249,"line":276},4,[247,278,280],{"class":279},"sutJx","# costs: (B, N_tracks, M_dets) of `1 - IoU`,\n",[247,282,284],{"class":249,"line":283},5,[247,285,286],{"class":279},"# with +inf where centroid distance > gate.\n",[247,288,290,293,297,301],{"class":249,"line":289},6,[247,291,292],{"class":257},"costs ",[247,294,296],{"class":295},"smGrS","=",[247,298,300],{"class":299},"slqww"," build_iou_cost_batch",[247,302,304],{"class":303},"sP7_E","(\n",[247,306,308,311,314,317,319,323,325,329],{"class":249,"line":307},7,[247,309,310],{"class":299},"    tracks",[247,312,313],{"class":303},",",[247,315,316],{"class":299}," detections",[247,318,313],{"class":303},[247,320,322],{"class":321},"s99_P"," gate",[247,324,296],{"class":295},[247,326,328],{"class":327},"srdBf","0.3",[247,330,331],{"class":303},",\n",[247,333,335,338],{"class":249,"line":334},8,[247,336,337],{"class":303},")",[247,339,340],{"class":279},"                                          # (B, N, M)\n",[247,342,344],{"class":249,"line":343},9,[247,345,273],{"emptyLinePlaceholder":272},[247,347,349,352,354,357,359,362,364,367,369,372,375,379,381,384],{"class":249,"line":348},10,[247,350,351],{"class":257},"matches",[247,353,313],{"class":303},[247,355,356],{"class":257}," ur",[247,358,313],{"class":303},[247,360,361],{"class":257}," uc",[247,363,313],{"class":303},[247,365,366],{"class":257}," n_matched ",[247,368,296],{"class":295},[247,370,371],{"class":257}," torchmatch",[247,373,374],{"class":303},".",[247,376,378],{"class":377},"skxfh","assignment",[247,380,374],{"class":303},[247,382,383],{"class":299},"solve",[247,385,304],{"class":303},[247,387,389,392,394,397,399,403],{"class":249,"line":388},11,[247,390,391],{"class":299},"    costs",[247,393,313],{"class":303},[247,395,396],{"class":321}," unpack",[247,398,296],{"class":295},[247,400,402],{"class":401},"s39Yj","True",[247,404,331],{"class":303},[247,406,408],{"class":249,"line":407},12,[247,409,410],{"class":303},")\n",[247,412,414],{"class":249,"line":413},13,[247,415,416],{"class":279},"# matches[b, :n_matched[b]] = (track_idx, det_idx) pairs\n",[199,418,419],{"v-slot:direct":176},[239,420,422],{"className":241,"code":421,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\ncosts = build_iou_cost_batch(\n    tracks, detections, gate=0.3,\n)                                          # (B, N, M)\n\n# AUTO would pick jonker_*_batch_unpacked; calling it directly lets\n# you choose dense (rectangular) vs compact (square AVX2-gather).\nout = torchmatch.assignment.ops.jonker_dense_batch_unpacked(costs)\nmatches, ur, uc, n_matched = out\n",[206,423,424,430,436,440,450,468,474,478,483,488,518],{"__ignoreMap":176},[247,425,426,428],{"class":249,"line":250},[247,427,254],{"class":253},[247,429,258],{"class":257},[247,431,432,434],{"class":249,"line":261},[247,433,254],{"class":253},[247,435,266],{"class":257},[247,437,438],{"class":249,"line":269},[247,439,273],{"emptyLinePlaceholder":272},[247,441,442,444,446,448],{"class":249,"line":276},[247,443,292],{"class":257},[247,445,296],{"class":295},[247,447,300],{"class":299},[247,449,304],{"class":303},[247,451,452,454,456,458,460,462,464,466],{"class":249,"line":283},[247,453,310],{"class":299},[247,455,313],{"class":303},[247,457,316],{"class":299},[247,459,313],{"class":303},[247,461,322],{"class":321},[247,463,296],{"class":295},[247,465,328],{"class":327},[247,467,331],{"class":303},[247,469,470,472],{"class":249,"line":289},[247,471,337],{"class":303},[247,473,340],{"class":279},[247,475,476],{"class":249,"line":307},[247,477,273],{"emptyLinePlaceholder":272},[247,479,480],{"class":249,"line":334},[247,481,482],{"class":279},"# AUTO would pick jonker_*_batch_unpacked; calling it directly lets\n",[247,484,485],{"class":249,"line":343},[247,486,487],{"class":279},"# you choose dense (rectangular) vs compact (square AVX2-gather).\n",[247,489,490,493,495,497,499,501,503,506,508,510,513,516],{"class":249,"line":348},[247,491,492],{"class":257},"out ",[247,494,296],{"class":295},[247,496,371],{"class":257},[247,498,374],{"class":303},[247,500,378],{"class":377},[247,502,374],{"class":303},[247,504,505],{"class":377},"ops",[247,507,374],{"class":303},[247,509,235],{"class":299},[247,511,512],{"class":303},"(",[247,514,515],{"class":299},"costs",[247,517,410],{"class":303},[247,519,520,522,524,526,528,530,532,534,536],{"class":249,"line":388},[247,521,351],{"class":257},[247,523,313],{"class":303},[247,525,356],{"class":257},[247,527,313],{"class":303},[247,529,361],{"class":257},[247,531,313],{"class":303},[247,533,366],{"class":257},[247,535,296],{"class":295},[247,537,538],{"class":257}," out\n",[193,540,546,567],{":related":541,"domain":542,"index":543,"systems":544,"title":545},"[{\"label\":\"Single-problem tutorial\",\"to\":\"\u002Falgorithms\u002Fassignment\u002Fquickstart\"},{\"label\":\"Choosing the right op\",\"to\":\"\u002Falgorithms\u002Fassignment\u002Fchoosing\"}]","Set prediction","02","DETR · MaskFormer · Mask2Former · RT-DETR","Hungarian matcher for the loss",[199,547,548,558],{"v-slot:prose":176},[202,549,550,551,554,555,557],{},"DETR-style object detectors find the lowest-cost one-to-one pairing (the Hungarian assignment) between model predictions and ground-truth targets before computing the per-pair loss term. The matcher runs once per image every training step, so it dominates training wall time when called naively in a Python loop. Batch the per-image cost matrices into one ",[206,552,553],{},"(B, N_pred, N_gt)"," tensor; ",[206,556,216],{}," solves them all at once.",[202,559,560,561,564,565,374],{},"When every problem is square (prediction count fixed, GT padded to the same count), ",[206,562,563],{},"jonker_compact_batch"," runs the tighter AVX2-gather inner loop on CPU. For mixed shapes, use ",[206,566,216],{},[199,568,569],{"v-slot:code":176},[232,570,571,672],{"default":234,"direct-label":216},[199,572,573],{"v-slot:match":176},[239,574,576],{"className":241,"code":575,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\n# Combined L1 + GIoU + class cost between every\n# prediction and every ground-truth target, padded\n# to a common N_gt with +inf in padded columns.\ncosts = compute_match_cost(preds, targets)\n# → (B, N_pred, N_gt + N_pad)\n\nmatches = torchmatch.assignment.solve(costs)\n# matches[b, i] == -1 marks pad-column hits;\n# mask them out of the per-pair loss.\n",[206,577,578,584,590,594,599,604,609,630,635,639,662,667],{"__ignoreMap":176},[247,579,580,582],{"class":249,"line":250},[247,581,254],{"class":253},[247,583,258],{"class":257},[247,585,586,588],{"class":249,"line":261},[247,587,254],{"class":253},[247,589,266],{"class":257},[247,591,592],{"class":249,"line":269},[247,593,273],{"emptyLinePlaceholder":272},[247,595,596],{"class":249,"line":276},[247,597,598],{"class":279},"# Combined L1 + GIoU + class cost between every\n",[247,600,601],{"class":249,"line":283},[247,602,603],{"class":279},"# prediction and every ground-truth target, padded\n",[247,605,606],{"class":249,"line":289},[247,607,608],{"class":279},"# to a common N_gt with +inf in padded columns.\n",[247,610,611,613,615,618,620,623,625,628],{"class":249,"line":307},[247,612,292],{"class":257},[247,614,296],{"class":295},[247,616,617],{"class":299}," compute_match_cost",[247,619,512],{"class":303},[247,621,622],{"class":299},"preds",[247,624,313],{"class":303},[247,626,627],{"class":299}," targets",[247,629,410],{"class":303},[247,631,632],{"class":249,"line":334},[247,633,634],{"class":279},"# → (B, N_pred, N_gt + N_pad)\n",[247,636,637],{"class":249,"line":343},[247,638,273],{"emptyLinePlaceholder":272},[247,640,641,644,646,648,650,652,654,656,658,660],{"class":249,"line":348},[247,642,643],{"class":257},"matches ",[247,645,296],{"class":295},[247,647,371],{"class":257},[247,649,374],{"class":303},[247,651,378],{"class":377},[247,653,374],{"class":303},[247,655,383],{"class":299},[247,657,512],{"class":303},[247,659,515],{"class":299},[247,661,410],{"class":303},[247,663,664],{"class":249,"line":388},[247,665,666],{"class":279},"# matches[b, i] == -1 marks pad-column hits;\n",[247,668,669],{"class":249,"line":407},[247,670,671],{"class":279},"# mask them out of the per-pair loss.\n",[199,673,674],{"v-slot:direct":176},[239,675,677],{"className":241,"code":676,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\ncosts = compute_match_cost(preds, targets)\n\n# When every problem is square, jonker_compact_batch runs the tighter\n# AVX2-gather inner loop on CPU. For mixed shapes, use jonker_dense_batch.\nmatches = torchmatch.assignment.ops.jonker_dense_batch(costs)\n",[206,678,679,685,691,695,713,717,722,727],{"__ignoreMap":176},[247,680,681,683],{"class":249,"line":250},[247,682,254],{"class":253},[247,684,258],{"class":257},[247,686,687,689],{"class":249,"line":261},[247,688,254],{"class":253},[247,690,266],{"class":257},[247,692,693],{"class":249,"line":269},[247,694,273],{"emptyLinePlaceholder":272},[247,696,697,699,701,703,705,707,709,711],{"class":249,"line":276},[247,698,292],{"class":257},[247,700,296],{"class":295},[247,702,617],{"class":299},[247,704,512],{"class":303},[247,706,622],{"class":299},[247,708,313],{"class":303},[247,710,627],{"class":299},[247,712,410],{"class":303},[247,714,715],{"class":249,"line":283},[247,716,273],{"emptyLinePlaceholder":272},[247,718,719],{"class":249,"line":289},[247,720,721],{"class":279},"# When every problem is square, jonker_compact_batch runs the tighter\n",[247,723,724],{"class":249,"line":307},[247,725,726],{"class":279},"# AVX2-gather inner loop on CPU. For mixed shapes, use jonker_dense_batch.\n",[247,728,729,731,733,735,737,739,741,743,745,747,749,751],{"class":249,"line":334},[247,730,643],{"class":257},[247,732,296],{"class":295},[247,734,371],{"class":257},[247,736,374],{"class":303},[247,738,378],{"class":377},[247,740,374],{"class":303},[247,742,505],{"class":377},[247,744,374],{"class":303},[247,746,216],{"class":299},[247,748,512],{"class":303},[247,750,515],{"class":299},[247,752,410],{"class":303},[193,754,760,784],{":related":755,"domain":756,"index":757,"systems":758,"title":759},"[{\"label\":\"jonker_compact details\",\"to\":\"\u002Falgorithms\u002Fassignment\u002Freference\"},{\"label\":\"Algorithms overview\",\"to\":\"\u002Falgorithms\u002Fassignment\u002Falgorithms\"}]","Evaluation","03","Unsupervised semseg · ARI · ReID · Codebook alignment","Cluster label matching",[199,761,762,765],{"v-slot:prose":176},[202,763,764],{},"Clustering algorithms assign arbitrary label numbers, so \"cluster 0\" in one run may correspond to \"cluster 2\" in another. Metrics like Adjusted Rand Index therefore need to find the optimal one-to-one relabelling between predicted and ground-truth cluster IDs before counting agreements — that relabelling is exactly the assignment problem. Build the cost as the negative confusion-matrix entry; the linear assignment solver (LAP) returns the optimal cluster-to-cluster mapping.",[202,766,767,768,771,772,775,776,779,780,783],{},"Square dense costs at moderate ",[206,769,770],{},"K"," (≤ 256) are the regime ",[206,773,774],{},"jonker_compact"," was built for. For ",[206,777,778],{},"K ≥ 512",", ",[206,781,782],{},"jonker_dense"," takes over.",[199,785,786],{"v-slot:code":176},[232,787,788,934],{"default":234,"direct-label":774},[199,789,790],{"v-slot:match":176},[239,791,793],{"className":241,"code":792,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\n# K x K confusion-style matrix. Negate so the LAP\n# minimizes disagreement instead of maximizing it.\ncost = -confusion_matrix.to(torch.float32)\nmapping = torchmatch.assignment.solve(cost)               # (K,)\n\nrelabelled = mapping[predicted_labels]\naccuracy = (relabelled == ground_truth).float().mean()\n",[206,794,795,801,807,811,816,821,851,878,882,901],{"__ignoreMap":176},[247,796,797,799],{"class":249,"line":250},[247,798,254],{"class":253},[247,800,258],{"class":257},[247,802,803,805],{"class":249,"line":261},[247,804,254],{"class":253},[247,806,266],{"class":257},[247,808,809],{"class":249,"line":269},[247,810,273],{"emptyLinePlaceholder":272},[247,812,813],{"class":249,"line":276},[247,814,815],{"class":279},"# K x K confusion-style matrix. Negate so the LAP\n",[247,817,818],{"class":249,"line":283},[247,819,820],{"class":279},"# minimizes disagreement instead of maximizing it.\n",[247,822,823,826,828,831,834,836,839,841,844,846,849],{"class":249,"line":289},[247,824,825],{"class":257},"cost ",[247,827,296],{"class":295},[247,829,830],{"class":295}," -",[247,832,833],{"class":257},"confusion_matrix",[247,835,374],{"class":303},[247,837,838],{"class":299},"to",[247,840,512],{"class":303},[247,842,843],{"class":299},"torch",[247,845,374],{"class":303},[247,847,848],{"class":377},"float32",[247,850,410],{"class":303},[247,852,853,856,858,860,862,864,866,868,870,873,875],{"class":249,"line":307},[247,854,855],{"class":257},"mapping ",[247,857,296],{"class":295},[247,859,371],{"class":257},[247,861,374],{"class":303},[247,863,378],{"class":377},[247,865,374],{"class":303},[247,867,383],{"class":299},[247,869,512],{"class":303},[247,871,872],{"class":299},"cost",[247,874,337],{"class":303},[247,876,877],{"class":279},"               # (K,)\n",[247,879,880],{"class":249,"line":334},[247,881,273],{"emptyLinePlaceholder":272},[247,883,884,887,889,892,895,898],{"class":249,"line":343},[247,885,886],{"class":257},"relabelled ",[247,888,296],{"class":295},[247,890,891],{"class":257}," mapping",[247,893,894],{"class":303},"[",[247,896,897],{"class":257},"predicted_labels",[247,899,900],{"class":303},"]\n",[247,902,903,906,908,911,913,916,919,922,925,928,931],{"class":249,"line":348},[247,904,905],{"class":257},"accuracy ",[247,907,296],{"class":295},[247,909,910],{"class":303}," (",[247,912,886],{"class":257},[247,914,915],{"class":295},"==",[247,917,918],{"class":257}," ground_truth",[247,920,921],{"class":303},").",[247,923,924],{"class":299},"float",[247,926,927],{"class":303},"().",[247,929,930],{"class":299},"mean",[247,932,933],{"class":303},"()\n",[199,935,936],{"v-slot:direct":176},[239,937,939],{"className":241,"code":938,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\ncost = -confusion_matrix.to(torch.float32)\n\n# Square dense costs at moderate K (≤ 256) are the regime\n# jonker_compact was built for. For K ≥ 512, jonker_dense takes over.\nmapping = torchmatch.assignment.ops.jonker_compact(cost)      # (K,)\nrelabelled = mapping[predicted_labels]\n",[206,940,941,947,953,957,981,985,990,995,1024],{"__ignoreMap":176},[247,942,943,945],{"class":249,"line":250},[247,944,254],{"class":253},[247,946,258],{"class":257},[247,948,949,951],{"class":249,"line":261},[247,950,254],{"class":253},[247,952,266],{"class":257},[247,954,955],{"class":249,"line":269},[247,956,273],{"emptyLinePlaceholder":272},[247,958,959,961,963,965,967,969,971,973,975,977,979],{"class":249,"line":276},[247,960,825],{"class":257},[247,962,296],{"class":295},[247,964,830],{"class":295},[247,966,833],{"class":257},[247,968,374],{"class":303},[247,970,838],{"class":299},[247,972,512],{"class":303},[247,974,843],{"class":299},[247,976,374],{"class":303},[247,978,848],{"class":377},[247,980,410],{"class":303},[247,982,983],{"class":249,"line":283},[247,984,273],{"emptyLinePlaceholder":272},[247,986,987],{"class":249,"line":289},[247,988,989],{"class":279},"# Square dense costs at moderate K (≤ 256) are the regime\n",[247,991,992],{"class":249,"line":307},[247,993,994],{"class":279},"# jonker_compact was built for. For K ≥ 512, jonker_dense takes over.\n",[247,996,997,999,1001,1003,1005,1007,1009,1011,1013,1015,1017,1019,1021],{"class":249,"line":334},[247,998,855],{"class":257},[247,1000,296],{"class":295},[247,1002,371],{"class":257},[247,1004,374],{"class":303},[247,1006,378],{"class":377},[247,1008,374],{"class":303},[247,1010,505],{"class":377},[247,1012,374],{"class":303},[247,1014,774],{"class":299},[247,1016,512],{"class":303},[247,1018,872],{"class":299},[247,1020,337],{"class":303},[247,1022,1023],{"class":279},"      # (K,)\n",[247,1025,1026,1028,1030,1032,1034,1036],{"class":249,"line":343},[247,1027,886],{"class":257},[247,1029,296],{"class":295},[247,1031,891],{"class":257},[247,1033,894],{"class":303},[247,1035,897],{"class":257},[247,1037,900],{"class":303},[193,1039,1045,1067],{":related":1040,"domain":1041,"index":1042,"systems":1043,"title":1044},"[{\"label\":\"Single-problem tutorial\",\"to\":\"\u002Falgorithms\u002Fassignment\u002Fquickstart\"},{\"label\":\"Benchmarks\",\"to\":\"\u002Fresources\u002Fbenchmarks\"}]","Drop-in","04","scipy.optimize.linear_sum_assignment · Hungarian solvers","A faster scipy LAP",[199,1046,1047],{"v-slot:prose":176},[202,1048,1049,1050,1052,1053,1055,1056,1059,1060,1062,1063,1066],{},"When a linear assignment problem (LAP) appears in a tight Python loop and SciPy is the bottleneck, swap the call. ",[206,1051,782],{}," produces the same optimal cost, accepts the same rectangular input, and stays inside ",[206,1054,843],{}," so the cost matrix never round-trips through ",[206,1057,1058],{},"numpy",". For a batch of independent LAPs, ",[206,1061,216],{}," runs ",[206,1064,1065],{},"at::parallel_for"," across problems on CPU or the tiled CUDA kernel on GPU.",[199,1068,1069],{"v-slot:code":176},[232,1070,1071,1225],{"default":234,"direct-label":782},[199,1072,1073],{"v-slot:match":176},[239,1074,1076],{"className":241,"code":1075,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\n# Before:\n#   from scipy.optimize import linear_sum_assignment\n#   r, c = linear_sum_assignment(cost.cpu().numpy())\n\n# After: stays in torch, batches naturally.\nrow_to_col = torchmatch.assignment.solve(cost)\nmatched = row_to_col >= 0\ntotal = cost[\n    torch.arange(cost.size(0))[matched],\n    row_to_col[matched],\n].sum()\n",[206,1077,1078,1084,1090,1094,1099,1104,1109,1113,1118,1141,1157,1170,1203,1214],{"__ignoreMap":176},[247,1079,1080,1082],{"class":249,"line":250},[247,1081,254],{"class":253},[247,1083,258],{"class":257},[247,1085,1086,1088],{"class":249,"line":261},[247,1087,254],{"class":253},[247,1089,266],{"class":257},[247,1091,1092],{"class":249,"line":269},[247,1093,273],{"emptyLinePlaceholder":272},[247,1095,1096],{"class":249,"line":276},[247,1097,1098],{"class":279},"# Before:\n",[247,1100,1101],{"class":249,"line":283},[247,1102,1103],{"class":279},"#   from scipy.optimize import linear_sum_assignment\n",[247,1105,1106],{"class":249,"line":289},[247,1107,1108],{"class":279},"#   r, c = linear_sum_assignment(cost.cpu().numpy())\n",[247,1110,1111],{"class":249,"line":307},[247,1112,273],{"emptyLinePlaceholder":272},[247,1114,1115],{"class":249,"line":334},[247,1116,1117],{"class":279},"# After: stays in torch, batches naturally.\n",[247,1119,1120,1123,1125,1127,1129,1131,1133,1135,1137,1139],{"class":249,"line":343},[247,1121,1122],{"class":257},"row_to_col ",[247,1124,296],{"class":295},[247,1126,371],{"class":257},[247,1128,374],{"class":303},[247,1130,378],{"class":377},[247,1132,374],{"class":303},[247,1134,383],{"class":299},[247,1136,512],{"class":303},[247,1138,872],{"class":299},[247,1140,410],{"class":303},[247,1142,1143,1146,1148,1151,1154],{"class":249,"line":348},[247,1144,1145],{"class":257},"matched ",[247,1147,296],{"class":295},[247,1149,1150],{"class":257}," row_to_col ",[247,1152,1153],{"class":295},">=",[247,1155,1156],{"class":327}," 0\n",[247,1158,1159,1162,1164,1167],{"class":249,"line":388},[247,1160,1161],{"class":257},"total ",[247,1163,296],{"class":295},[247,1165,1166],{"class":257}," cost",[247,1168,1169],{"class":303},"[\n",[247,1171,1172,1175,1177,1180,1182,1184,1186,1189,1191,1194,1197,1200],{"class":249,"line":407},[247,1173,1174],{"class":257},"    torch",[247,1176,374],{"class":303},[247,1178,1179],{"class":299},"arange",[247,1181,512],{"class":303},[247,1183,872],{"class":299},[247,1185,374],{"class":303},[247,1187,1188],{"class":299},"size",[247,1190,512],{"class":303},[247,1192,1193],{"class":327},"0",[247,1195,1196],{"class":303},"))[",[247,1198,1199],{"class":257},"matched",[247,1201,1202],{"class":303},"],\n",[247,1204,1205,1208,1210,1212],{"class":249,"line":413},[247,1206,1207],{"class":257},"    row_to_col",[247,1209,894],{"class":303},[247,1211,1199],{"class":257},[247,1213,1202],{"class":303},[247,1215,1217,1220,1223],{"class":249,"line":1216},14,[247,1218,1219],{"class":303},"].",[247,1221,1222],{"class":299},"sum",[247,1224,933],{"class":303},[199,1226,1227],{"v-slot:direct":176},[239,1228,1230],{"className":241,"code":1229,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\n# jonker_dense is the rectangular-capable CPU op; AUTO routes here\n# for any non-tiny CPU problem.\nrow_to_col = torchmatch.assignment.ops.jonker_dense(cost)\nmatched = row_to_col >= 0\ntotal = cost[\n    torch.arange(cost.size(0))[matched],\n    row_to_col[matched],\n].sum()\n",[206,1231,1232,1238,1244,1248,1253,1258,1284,1296,1306,1332,1342],{"__ignoreMap":176},[247,1233,1234,1236],{"class":249,"line":250},[247,1235,254],{"class":253},[247,1237,258],{"class":257},[247,1239,1240,1242],{"class":249,"line":261},[247,1241,254],{"class":253},[247,1243,266],{"class":257},[247,1245,1246],{"class":249,"line":269},[247,1247,273],{"emptyLinePlaceholder":272},[247,1249,1250],{"class":249,"line":276},[247,1251,1252],{"class":279},"# jonker_dense is the rectangular-capable CPU op; AUTO routes here\n",[247,1254,1255],{"class":249,"line":283},[247,1256,1257],{"class":279},"# for any non-tiny CPU problem.\n",[247,1259,1260,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282],{"class":249,"line":289},[247,1261,1122],{"class":257},[247,1263,296],{"class":295},[247,1265,371],{"class":257},[247,1267,374],{"class":303},[247,1269,378],{"class":377},[247,1271,374],{"class":303},[247,1273,505],{"class":377},[247,1275,374],{"class":303},[247,1277,782],{"class":299},[247,1279,512],{"class":303},[247,1281,872],{"class":299},[247,1283,410],{"class":303},[247,1285,1286,1288,1290,1292,1294],{"class":249,"line":307},[247,1287,1145],{"class":257},[247,1289,296],{"class":295},[247,1291,1150],{"class":257},[247,1293,1153],{"class":295},[247,1295,1156],{"class":327},[247,1297,1298,1300,1302,1304],{"class":249,"line":334},[247,1299,1161],{"class":257},[247,1301,296],{"class":295},[247,1303,1166],{"class":257},[247,1305,1169],{"class":303},[247,1307,1308,1310,1312,1314,1316,1318,1320,1322,1324,1326,1328,1330],{"class":249,"line":343},[247,1309,1174],{"class":257},[247,1311,374],{"class":303},[247,1313,1179],{"class":299},[247,1315,512],{"class":303},[247,1317,872],{"class":299},[247,1319,374],{"class":303},[247,1321,1188],{"class":299},[247,1323,512],{"class":303},[247,1325,1193],{"class":327},[247,1327,1196],{"class":303},[247,1329,1199],{"class":257},[247,1331,1202],{"class":303},[247,1333,1334,1336,1338,1340],{"class":249,"line":348},[247,1335,1207],{"class":257},[247,1337,894],{"class":303},[247,1339,1199],{"class":257},[247,1341,1202],{"class":303},[247,1343,1344,1346,1348],{"class":249,"line":388},[247,1345,1219],{"class":303},[247,1347,1222],{"class":299},[247,1349,933],{"class":303},[190,1351],{"label":90},[193,1353,1358,1379],{":related":1354,"domain":1355,"index":186,"systems":1356,"title":1357},"[{\"label\":\"Transport ops reference\",\"to\":\"\u002Falgorithms\u002Ftransport\u002Freference\"}]","Geometric learning","PointNet · ShapeFlow · 3D-LFM · Wasserstein AE","Point-cloud Wasserstein loss",[199,1359,1360],{"v-slot:prose":176},[202,1361,1362,1363,1366,1367,1370,1371,1374,1375,1378],{},"For training generative models over point sets, ",[206,1364,1365],{},"transport.samples.loss(x, y)"," computes the Sinkhorn approximation of the Wasserstein distance between two point clouds — a measure of how much work it takes to move one distribution onto the other — without ever building the full N×M pairwise-cost matrix in memory. A Triton streaming kernel handles the computation directly in fast CUDA registers. Gradients flow analytically through both ",[206,1368,1369],{},"x"," and ",[206,1372,1373],{},"y","; pass ",[206,1376,1377],{},"debias=True"," for the symmetric Sinkhorn divergence variant.",[199,1380,1381],{"v-slot:code":176},[232,1382,1384,1511],{"default":234,"direct-label":1383},"samples.loss",[199,1385,1386],{"v-slot:match":176},[239,1387,1389],{"className":241,"code":1388,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\n# predicted and ground-truth 3-D point clouds\npred = model(z)                                 # (N, 3)\ngt   = target_cloud.to(pred.device)             # (M, 3)\n\nloss = torchmatch.transport.samples.loss(pred, gt)\nloss.backward()\n",[206,1390,1391,1397,1403,1407,1412,1432,1461,1465,1500],{"__ignoreMap":176},[247,1392,1393,1395],{"class":249,"line":250},[247,1394,254],{"class":253},[247,1396,258],{"class":257},[247,1398,1399,1401],{"class":249,"line":261},[247,1400,254],{"class":253},[247,1402,266],{"class":257},[247,1404,1405],{"class":249,"line":269},[247,1406,273],{"emptyLinePlaceholder":272},[247,1408,1409],{"class":249,"line":276},[247,1410,1411],{"class":279},"# predicted and ground-truth 3-D point clouds\n",[247,1413,1414,1417,1419,1422,1424,1427,1429],{"class":249,"line":283},[247,1415,1416],{"class":257},"pred ",[247,1418,296],{"class":295},[247,1420,1421],{"class":299}," model",[247,1423,512],{"class":303},[247,1425,1426],{"class":299},"z",[247,1428,337],{"class":303},[247,1430,1431],{"class":279},"                                 # (N, 3)\n",[247,1433,1434,1437,1439,1442,1444,1446,1448,1451,1453,1456,1458],{"class":249,"line":289},[247,1435,1436],{"class":257},"gt   ",[247,1438,296],{"class":295},[247,1440,1441],{"class":257}," target_cloud",[247,1443,374],{"class":303},[247,1445,838],{"class":299},[247,1447,512],{"class":303},[247,1449,1450],{"class":299},"pred",[247,1452,374],{"class":303},[247,1454,1455],{"class":377},"device",[247,1457,337],{"class":303},[247,1459,1460],{"class":279},"             # (M, 3)\n",[247,1462,1463],{"class":249,"line":307},[247,1464,273],{"emptyLinePlaceholder":272},[247,1466,1467,1470,1472,1474,1476,1479,1481,1484,1486,1489,1491,1493,1495,1498],{"class":249,"line":334},[247,1468,1469],{"class":257},"loss ",[247,1471,296],{"class":295},[247,1473,371],{"class":257},[247,1475,374],{"class":303},[247,1477,1478],{"class":377},"transport",[247,1480,374],{"class":303},[247,1482,1483],{"class":377},"samples",[247,1485,374],{"class":303},[247,1487,1488],{"class":299},"loss",[247,1490,512],{"class":303},[247,1492,1450],{"class":299},[247,1494,313],{"class":303},[247,1496,1497],{"class":299}," gt",[247,1499,410],{"class":303},[247,1501,1502,1504,1506,1509],{"class":249,"line":343},[247,1503,1488],{"class":257},[247,1505,374],{"class":303},[247,1507,1508],{"class":299},"backward",[247,1510,933],{"class":303},[199,1512,1513],{"v-slot:direct":176},[239,1514,1516],{"className":241,"code":1515,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\npred = model(z)\ngt   = target_cloud.to(pred.device)\n\n# debias=True gives the Sinkhorn divergence variant:\n# symmetric, positive, corrects for self-transport.\nloss = torchmatch.transport.samples.loss(\n    pred, gt, debias=True,\n)\nloss.backward()\n",[206,1517,1518,1524,1530,1534,1548,1570,1574,1579,1584,1606,1626,1630],{"__ignoreMap":176},[247,1519,1520,1522],{"class":249,"line":250},[247,1521,254],{"class":253},[247,1523,258],{"class":257},[247,1525,1526,1528],{"class":249,"line":261},[247,1527,254],{"class":253},[247,1529,266],{"class":257},[247,1531,1532],{"class":249,"line":269},[247,1533,273],{"emptyLinePlaceholder":272},[247,1535,1536,1538,1540,1542,1544,1546],{"class":249,"line":276},[247,1537,1416],{"class":257},[247,1539,296],{"class":295},[247,1541,1421],{"class":299},[247,1543,512],{"class":303},[247,1545,1426],{"class":299},[247,1547,410],{"class":303},[247,1549,1550,1552,1554,1556,1558,1560,1562,1564,1566,1568],{"class":249,"line":283},[247,1551,1436],{"class":257},[247,1553,296],{"class":295},[247,1555,1441],{"class":257},[247,1557,374],{"class":303},[247,1559,838],{"class":299},[247,1561,512],{"class":303},[247,1563,1450],{"class":299},[247,1565,374],{"class":303},[247,1567,1455],{"class":377},[247,1569,410],{"class":303},[247,1571,1572],{"class":249,"line":289},[247,1573,273],{"emptyLinePlaceholder":272},[247,1575,1576],{"class":249,"line":307},[247,1577,1578],{"class":279},"# debias=True gives the Sinkhorn divergence variant:\n",[247,1580,1581],{"class":249,"line":334},[247,1582,1583],{"class":279},"# symmetric, positive, corrects for self-transport.\n",[247,1585,1586,1588,1590,1592,1594,1596,1598,1600,1602,1604],{"class":249,"line":343},[247,1587,1469],{"class":257},[247,1589,296],{"class":295},[247,1591,371],{"class":257},[247,1593,374],{"class":303},[247,1595,1478],{"class":377},[247,1597,374],{"class":303},[247,1599,1483],{"class":377},[247,1601,374],{"class":303},[247,1603,1488],{"class":299},[247,1605,304],{"class":303},[247,1607,1608,1611,1613,1615,1617,1620,1622,1624],{"class":249,"line":348},[247,1609,1610],{"class":299},"    pred",[247,1612,313],{"class":303},[247,1614,1497],{"class":299},[247,1616,313],{"class":303},[247,1618,1619],{"class":321}," debias",[247,1621,296],{"class":295},[247,1623,402],{"class":401},[247,1625,331],{"class":303},[247,1627,1628],{"class":249,"line":388},[247,1629,410],{"class":303},[247,1631,1632,1634,1636,1638],{"class":249,"line":407},[247,1633,1488],{"class":257},[247,1635,374],{"class":303},[247,1637,1508],{"class":299},[247,1639,933],{"class":303},[193,1641,1645,1670],{":related":1354,"domain":1642,"index":543,"systems":1643,"title":1644},"Set prediction \u002F training","Slot Attention · Soft-DETR · Differentiable permutations","Differentiable soft matching",[199,1646,1647],{"v-slot:prose":176},[202,1648,1649,1650,1653,1654,1657,1658,1661,1662,1665,1666,1669],{},"Hard one-to-one matching (like the Hungarian algorithm) is not differentiable — gradients cannot flow back through a discrete matching step. Replace it with a Sinkhorn plan: ",[206,1651,1652],{},"transport.matrix.solve(cost)"," returns a regularized transport plan T ∈ ",[247,1655,1656],{},"0,1","^{N×M} — fractional soft assignments stored in log-domain for numerical stability — that differentiate smoothly through the cost matrix. Reduce the regularization (",[206,1659,1660],{},"reg",") toward zero to sharpen the plan toward a hard permutation. Both 2-D ",[206,1663,1664],{},"(N, M)"," and batched 3-D ",[206,1667,1668],{},"(B, N, M)"," cost tensors are accepted.",[199,1671,1672],{"v-slot:code":176},[232,1673,1675,1877],{"default":234,"direct-label":1674},"ops.log_sinkhorn",[199,1676,1677],{"v-slot:match":176},[239,1678,1680],{"className":241,"code":1679,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\nfrom torchmatch.transport.matrix import Backend\n\n# (B, N, M) cost: negative cosine or L2 similarity\ncost = -torch.einsum(\"bnd,bmd->bnm\", pred_feats, gt_feats)\n\n# log-plan (B, N, M); differentiable w.r.t. cost\nlog_plan = torchmatch.transport.matrix.solve(\n    cost,\n    backend=Backend.LOG_SINKHORN,\n)\nloss = (log_plan.exp() * cost).sum(-1).mean()\nloss.backward()\n",[206,1681,1682,1688,1694,1715,1719,1724,1763,1767,1772,1796,1803,1821,1825,1867],{"__ignoreMap":176},[247,1683,1684,1686],{"class":249,"line":250},[247,1685,254],{"class":253},[247,1687,258],{"class":257},[247,1689,1690,1692],{"class":249,"line":261},[247,1691,254],{"class":253},[247,1693,266],{"class":257},[247,1695,1696,1699,1701,1703,1705,1707,1710,1712],{"class":249,"line":269},[247,1697,1698],{"class":253},"from",[247,1700,371],{"class":257},[247,1702,374],{"class":303},[247,1704,1478],{"class":257},[247,1706,374],{"class":303},[247,1708,1709],{"class":257},"matrix ",[247,1711,254],{"class":253},[247,1713,1714],{"class":257}," Backend\n",[247,1716,1717],{"class":249,"line":276},[247,1718,273],{"emptyLinePlaceholder":272},[247,1720,1721],{"class":249,"line":283},[247,1722,1723],{"class":279},"# (B, N, M) cost: negative cosine or L2 similarity\n",[247,1725,1726,1728,1730,1732,1734,1736,1739,1741,1745,1749,1751,1753,1756,1758,1761],{"class":249,"line":289},[247,1727,825],{"class":257},[247,1729,296],{"class":295},[247,1731,830],{"class":295},[247,1733,843],{"class":257},[247,1735,374],{"class":303},[247,1737,1738],{"class":299},"einsum",[247,1740,512],{"class":303},[247,1742,1744],{"class":1743},"sjJ54","\"",[247,1746,1748],{"class":1747},"s_sjI","bnd,bmd->bnm",[247,1750,1744],{"class":1743},[247,1752,313],{"class":303},[247,1754,1755],{"class":299}," pred_feats",[247,1757,313],{"class":303},[247,1759,1760],{"class":299}," gt_feats",[247,1762,410],{"class":303},[247,1764,1765],{"class":249,"line":307},[247,1766,273],{"emptyLinePlaceholder":272},[247,1768,1769],{"class":249,"line":334},[247,1770,1771],{"class":279},"# log-plan (B, N, M); differentiable w.r.t. cost\n",[247,1773,1774,1777,1779,1781,1783,1785,1787,1790,1792,1794],{"class":249,"line":343},[247,1775,1776],{"class":257},"log_plan ",[247,1778,296],{"class":295},[247,1780,371],{"class":257},[247,1782,374],{"class":303},[247,1784,1478],{"class":377},[247,1786,374],{"class":303},[247,1788,1789],{"class":377},"matrix",[247,1791,374],{"class":303},[247,1793,383],{"class":299},[247,1795,304],{"class":303},[247,1797,1798,1801],{"class":249,"line":348},[247,1799,1800],{"class":299},"    cost",[247,1802,331],{"class":303},[247,1804,1805,1808,1810,1813,1815,1819],{"class":249,"line":388},[247,1806,1807],{"class":321},"    backend",[247,1809,296],{"class":295},[247,1811,1812],{"class":299},"Backend",[247,1814,374],{"class":303},[247,1816,1818],{"class":1817},"swQdS","LOG_SINKHORN",[247,1820,331],{"class":303},[247,1822,1823],{"class":249,"line":407},[247,1824,410],{"class":303},[247,1826,1827,1829,1831,1833,1836,1838,1841,1844,1847,1849,1851,1853,1855,1858,1861,1863,1865],{"class":249,"line":413},[247,1828,1469],{"class":257},[247,1830,296],{"class":295},[247,1832,910],{"class":303},[247,1834,1835],{"class":257},"log_plan",[247,1837,374],{"class":303},[247,1839,1840],{"class":299},"exp",[247,1842,1843],{"class":303},"()",[247,1845,1846],{"class":295}," *",[247,1848,1166],{"class":257},[247,1850,921],{"class":303},[247,1852,1222],{"class":299},[247,1854,512],{"class":303},[247,1856,1857],{"class":295},"-",[247,1859,1860],{"class":327},"1",[247,1862,921],{"class":303},[247,1864,930],{"class":299},[247,1866,933],{"class":303},[247,1868,1869,1871,1873,1875],{"class":249,"line":1216},[247,1870,1488],{"class":257},[247,1872,374],{"class":303},[247,1874,1508],{"class":299},[247,1876,933],{"class":303},[199,1878,1879],{"v-slot:direct":176},[239,1880,1882],{"className":241,"code":1881,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\ncost = -torch.einsum(\"bnd,bmd->bnm\", pred_feats, gt_feats)\n\n# Call the op directly to pin reg and n_iter.\nlog_plan = torchmatch.transport.matrix.ops.log_sinkhorn(\n    cost, reg=0.05, n_iter=50,\n)\n",[206,1883,1884,1890,1896,1900,1932,1936,1941,1968,1994],{"__ignoreMap":176},[247,1885,1886,1888],{"class":249,"line":250},[247,1887,254],{"class":253},[247,1889,258],{"class":257},[247,1891,1892,1894],{"class":249,"line":261},[247,1893,254],{"class":253},[247,1895,266],{"class":257},[247,1897,1898],{"class":249,"line":269},[247,1899,273],{"emptyLinePlaceholder":272},[247,1901,1902,1904,1906,1908,1910,1912,1914,1916,1918,1920,1922,1924,1926,1928,1930],{"class":249,"line":276},[247,1903,825],{"class":257},[247,1905,296],{"class":295},[247,1907,830],{"class":295},[247,1909,843],{"class":257},[247,1911,374],{"class":303},[247,1913,1738],{"class":299},[247,1915,512],{"class":303},[247,1917,1744],{"class":1743},[247,1919,1748],{"class":1747},[247,1921,1744],{"class":1743},[247,1923,313],{"class":303},[247,1925,1755],{"class":299},[247,1927,313],{"class":303},[247,1929,1760],{"class":299},[247,1931,410],{"class":303},[247,1933,1934],{"class":249,"line":283},[247,1935,273],{"emptyLinePlaceholder":272},[247,1937,1938],{"class":249,"line":289},[247,1939,1940],{"class":279},"# Call the op directly to pin reg and n_iter.\n",[247,1942,1943,1945,1947,1949,1951,1953,1955,1957,1959,1961,1963,1966],{"class":249,"line":307},[247,1944,1776],{"class":257},[247,1946,296],{"class":295},[247,1948,371],{"class":257},[247,1950,374],{"class":303},[247,1952,1478],{"class":377},[247,1954,374],{"class":303},[247,1956,1789],{"class":377},[247,1958,374],{"class":303},[247,1960,505],{"class":377},[247,1962,374],{"class":303},[247,1964,1965],{"class":299},"log_sinkhorn",[247,1967,304],{"class":303},[247,1969,1970,1972,1974,1977,1979,1982,1984,1987,1989,1992],{"class":249,"line":334},[247,1971,1800],{"class":299},[247,1973,313],{"class":303},[247,1975,1976],{"class":321}," reg",[247,1978,296],{"class":295},[247,1980,1981],{"class":327},"0.05",[247,1983,313],{"class":303},[247,1985,1986],{"class":321}," n_iter",[247,1988,296],{"class":295},[247,1990,1991],{"class":327},"50",[247,1993,331],{"class":303},[247,1995,1996],{"class":249,"line":343},[247,1997,410],{"class":303},[193,1999,2003,2030],{":related":1354,"domain":2000,"index":757,"systems":2001,"title":2002},"Domain adaptation \u002F robust OT","Domain shift · Partial shape matching · Noisy labels","Unbalanced transport",[199,2004,2005],{"v-slot:prose":176},[202,2006,2007,2008,2011,2012,2015,2016,779,2019,2022,2023,2025,2026,2029],{},"Standard optimal transport requires the source and target distributions to have exactly equal total mass — every point must be fully accounted for. If one set contains outliers or the two domains differ in size, this constraint forces those outliers into the plan and corrupts the result. ",[206,2009,2010],{},"UNBALANCED_SINKHORN"," relaxes the marginal constraints via a KL-divergence penalty controlled by the ",[206,2013,2014],{},"reach"," (or ",[206,2017,2018],{},"reach_x",[206,2020,2021],{},"reach_y",") parameter. Smaller ",[206,2024,2014],{}," is more lenient; ",[206,2027,2028],{},"reach → ∞"," recovers balanced OT. Both the matrix and samples faces support unbalanced mode.",[199,2031,2032],{"v-slot:code":176},[232,2033,2035,2165],{"default":234,"direct-label":2034},"samples.loss (reach)",[199,2036,2037],{"v-slot:match":176},[239,2038,2040],{"className":241,"code":2039,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\nfrom torchmatch.transport.matrix import Backend\n\ncost = compute_cost(source_features, target_features)\n\n# reach controls the KL marginal penalty; smaller = more lenient\nplan = torchmatch.transport.matrix.solve(\n    cost,\n    backend=Backend.UNBALANCED_SINKHORN,\n    reach=0.5,\n)\n",[206,2041,2042,2048,2054,2072,2076,2097,2101,2106,2129,2135,2149,2161],{"__ignoreMap":176},[247,2043,2044,2046],{"class":249,"line":250},[247,2045,254],{"class":253},[247,2047,258],{"class":257},[247,2049,2050,2052],{"class":249,"line":261},[247,2051,254],{"class":253},[247,2053,266],{"class":257},[247,2055,2056,2058,2060,2062,2064,2066,2068,2070],{"class":249,"line":269},[247,2057,1698],{"class":253},[247,2059,371],{"class":257},[247,2061,374],{"class":303},[247,2063,1478],{"class":257},[247,2065,374],{"class":303},[247,2067,1709],{"class":257},[247,2069,254],{"class":253},[247,2071,1714],{"class":257},[247,2073,2074],{"class":249,"line":276},[247,2075,273],{"emptyLinePlaceholder":272},[247,2077,2078,2080,2082,2085,2087,2090,2092,2095],{"class":249,"line":283},[247,2079,825],{"class":257},[247,2081,296],{"class":295},[247,2083,2084],{"class":299}," compute_cost",[247,2086,512],{"class":303},[247,2088,2089],{"class":299},"source_features",[247,2091,313],{"class":303},[247,2093,2094],{"class":299}," target_features",[247,2096,410],{"class":303},[247,2098,2099],{"class":249,"line":289},[247,2100,273],{"emptyLinePlaceholder":272},[247,2102,2103],{"class":249,"line":307},[247,2104,2105],{"class":279},"# reach controls the KL marginal penalty; smaller = more lenient\n",[247,2107,2108,2111,2113,2115,2117,2119,2121,2123,2125,2127],{"class":249,"line":334},[247,2109,2110],{"class":257},"plan ",[247,2112,296],{"class":295},[247,2114,371],{"class":257},[247,2116,374],{"class":303},[247,2118,1478],{"class":377},[247,2120,374],{"class":303},[247,2122,1789],{"class":377},[247,2124,374],{"class":303},[247,2126,383],{"class":299},[247,2128,304],{"class":303},[247,2130,2131,2133],{"class":249,"line":343},[247,2132,1800],{"class":299},[247,2134,331],{"class":303},[247,2136,2137,2139,2141,2143,2145,2147],{"class":249,"line":348},[247,2138,1807],{"class":321},[247,2140,296],{"class":295},[247,2142,1812],{"class":299},[247,2144,374],{"class":303},[247,2146,2010],{"class":1817},[247,2148,331],{"class":303},[247,2150,2151,2154,2156,2159],{"class":249,"line":388},[247,2152,2153],{"class":321},"    reach",[247,2155,296],{"class":295},[247,2157,2158],{"class":327},"0.5",[247,2160,331],{"class":303},[247,2162,2163],{"class":249,"line":407},[247,2164,410],{"class":303},[199,2166,2167],{"v-slot:direct":176},[239,2168,2170],{"className":241,"code":2169,"language":243,"meta":176,"style":176},"import torch\nimport torchmatch\n\nx = source_pts.cuda()\ny = target_pts.cuda()\n\n# Point-cloud unbalanced OT via samples face\nloss = torchmatch.transport.samples.loss(\n    x, y,\n    reach=0.5,      # or reach_x \u002F reach_y for asymmetric\n)\n",[206,2171,2172,2178,2184,2188,2205,2221,2225,2230,2252,2264,2277],{"__ignoreMap":176},[247,2173,2174,2176],{"class":249,"line":250},[247,2175,254],{"class":253},[247,2177,258],{"class":257},[247,2179,2180,2182],{"class":249,"line":261},[247,2181,254],{"class":253},[247,2183,266],{"class":257},[247,2185,2186],{"class":249,"line":269},[247,2187,273],{"emptyLinePlaceholder":272},[247,2189,2190,2193,2195,2198,2200,2203],{"class":249,"line":276},[247,2191,2192],{"class":257},"x ",[247,2194,296],{"class":295},[247,2196,2197],{"class":257}," source_pts",[247,2199,374],{"class":303},[247,2201,2202],{"class":299},"cuda",[247,2204,933],{"class":303},[247,2206,2207,2210,2212,2215,2217,2219],{"class":249,"line":283},[247,2208,2209],{"class":257},"y ",[247,2211,296],{"class":295},[247,2213,2214],{"class":257}," target_pts",[247,2216,374],{"class":303},[247,2218,2202],{"class":299},[247,2220,933],{"class":303},[247,2222,2223],{"class":249,"line":289},[247,2224,273],{"emptyLinePlaceholder":272},[247,2226,2227],{"class":249,"line":307},[247,2228,2229],{"class":279},"# Point-cloud unbalanced OT via samples face\n",[247,2231,2232,2234,2236,2238,2240,2242,2244,2246,2248,2250],{"class":249,"line":334},[247,2233,1469],{"class":257},[247,2235,296],{"class":295},[247,2237,371],{"class":257},[247,2239,374],{"class":303},[247,2241,1478],{"class":377},[247,2243,374],{"class":303},[247,2245,1483],{"class":377},[247,2247,374],{"class":303},[247,2249,1488],{"class":299},[247,2251,304],{"class":303},[247,2253,2254,2257,2259,2262],{"class":249,"line":343},[247,2255,2256],{"class":299},"    x",[247,2258,313],{"class":303},[247,2260,2261],{"class":299}," y",[247,2263,331],{"class":303},[247,2265,2266,2268,2270,2272,2274],{"class":249,"line":348},[247,2267,2153],{"class":321},[247,2269,296],{"class":295},[247,2271,2158],{"class":327},[247,2273,313],{"class":303},[247,2275,2276],{"class":279},"      # or reach_x \u002F reach_y for asymmetric\n",[247,2278,2279],{"class":249,"line":388},[247,2280,410],{"class":303},[184,2282,2285,2289],{"index":543,"subtitle":2283,"title":2284},"The assignment dispatcher routes by device, shape, and size. Seven rows of the decision tree; the full version is in the choosing tutorial.","What AUTO picks",[2286,2287],"landing-chooser",{":rows":2288},"[{\"have\":\"One cost matrix on CPU · \u003Ccode>N×M ≤ 64\u003C\u002Fcode>\",\"use\":\"jonker_scalar\",\"why\":\"Sequential reference; lowest overhead at tiny sizes\"},{\"have\":\"One cost matrix on CPU · square · any size\",\"use\":\"jonker_compact\",\"why\":\"Tightest AVX2-gather inner loop\"},{\"have\":\"One cost matrix on CPU · rectangular · any size\",\"use\":\"jonker_dense\",\"why\":\"AVX2 flat-pointer; rectangular-capable\"},{\"have\":\"A batch of cost matrices on CPU\",\"use\":\"jonker_compact_batch \u003Cspan style=\\\"color:var(--ui-color-text-dimmed)\\\">·or·\u003C\u002Fspan> jonker_dense_batch\",\"why\":\"\u003Ccode>at::parallel_for\u003C\u002Fcode> over problems; compact when square\"},{\"have\":\"A batch of square \u003Ccode>K ≤ 64\u003C\u002Fcode> matrices on CUDA\",\"use\":\"jonker_dense_batch (CUDA)\",\"why\":\"Single-block-per-problem tiled kernel; CUDA-graph-safe\"},{\"have\":\"One cost matrix on CUDA · \u003Ccode>N ≥ 32\u003C\u002Fcode>\",\"use\":\"lawler\",\"why\":\"Lawler's parallel-BFS tree augmentation; dense-favored\"},{\"have\":\"One cost matrix on CUDA · \u003Ccode>N &lt; 32\u003C\u002Fcode> or tied \u002F quantized\",\"use\":\"munkres\",\"why\":\"Munkres' single-path Hungarian; sparse-favored\"}]",[202,2290,2291],{},[2292,2293,2296],"a",{"className":2294,"href":38},[2295],"lc-link","Full decision tree →",[184,2298,2301,2304],{"index":757,"subtitle":2299,"title":2300},"The transport dispatcher resolves by problem type. Pick the backend that matches your cost representation, marginal constraints, and differentiability needs.","Transport backends",[2286,2302],{":rows":2303},"[{\"have\":\"Differentiable soft plan from cost matrix\",\"use\":\"LOG_SINKHORN\",\"why\":\"Default AUTO; Sinkhorn LSE loop; returns log-plan (B, N, M); grads w.r.t. cost\"},{\"have\":\"Symmetric OT metric \u002F training loss\",\"use\":\"SINKHORN_DIVERGENCE\",\"why\":\"Debiased; positive; symmetric; cancels self-transport bias; returns scalar per batch\"},{\"have\":\"Partial matching or unequal total mass\",\"use\":\"UNBALANCED_SINKHORN\",\"why\":\"KL-relaxed marginal constraints; tune via \u003Ccode>reach\u003C\u002Fcode>; handles outliers\"},{\"have\":\"Two raw point clouds on CUDA\",\"use\":\"transport.samples.loss\",\"why\":\"Triton streaming kernel; no N×M allocation; fuses cost + LSE; grads through both sets\"},{\"have\":\"Exact Earth Mover&apos;s Distance (no regularization)\",\"use\":\"EXACT_EMD\",\"why\":\"Network simplex; no bias; CPU-only; reference quality for small problems\"}]",[202,2305,2306],{},[2292,2307,2309],{"className":2308,"href":75},[2295],"Transport reference →",[2311,2312],"landing-cta",{":links":2313,":specs":2314,"command":2315},"[{\"label\":\"Quickstart →\",\"href\":\"\u002Fgetting-started\"},{\"label\":\"Tutorials\",\"href\":\"\u002Falgorithms\"},{\"label\":\"Source ↗\",\"href\":\"https:\u002F\u002Fgithub.com\u002Fkhwstolle\u002Ftorchmatch\",\"external\":true}]","[{\"key\":\"REQUIRES\",\"value\":\"Python 3.13 · PyTorch ≥ 2.11 · x86-64 Linux\"},{\"key\":\"CUDA WHEELS\",\"value\":\"cu126 · cu128 · cu130\"}]","$ pip install torchmatch",[2317,2318,2319],"style",{},"html pre.shiki code .sVHd0, html code.shiki .sVHd0{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#D73A49;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .su5hD, html code.shiki .su5hD{--shiki-light:#90A4AE;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sutJx, html code.shiki .sutJx{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#6A737D;--shiki-default-font-style:inherit;--shiki-dark:#6A737D;--shiki-dark-font-style:inherit}html pre.shiki code .smGrS, html code.shiki .smGrS{--shiki-light:#39ADB5;--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .slqww, html code.shiki .slqww{--shiki-light:#6182B8;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sP7_E, html code.shiki .sP7_E{--shiki-light:#39ADB5;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s99_P, html code.shiki .s99_P{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#E36209;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .srdBf, html code.shiki .srdBf{--shiki-light:#F76D47;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .skxfh, html code.shiki .skxfh{--shiki-light:#E53935;--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .s39Yj, html code.shiki .s39Yj{--shiki-light:#39ADB5;--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sjJ54, html code.shiki .sjJ54{--shiki-light:#39ADB5;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s_sjI, html code.shiki .s_sjI{--shiki-light:#91B859;--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .swQdS, html code.shiki .swQdS{--shiki-light:#E53935;--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":176,"searchDepth":269,"depth":269,"links":2321},[],"md",{},"\u002F",{"title":2326,"description":2327},"torchmatch — Assignment & Transport for PyTorch","Linear assignment and optimal transport solvers for PyTorch. Tracking-by-detection, DETR-style set prediction losses, cluster relabelling, Sinkhorn OT, and point-cloud Wasserstein loss — all batched, torch.compile-ready.","index","NvmTBbjfJLT-CFhsIu3iHLevWhMJSQV5Va7DTKo2Kfs",1785218158702]