<jupyter_start><jupyter_text>Advanced Linking: Subnetworks and `search_range` IntroductionThe goal of the linking step is to find the most likely set of assignments that match each feature in the previous frame with its counterpart in the current frame. This is not always trivial: the correct match for a feature is not always the one closest in distance, and some particles disappear or are introduced with each new frame, leaving  features dangling. In theory, doing this for $N$ particles involves evaluating all $N!$ possible sets of assignments, and choosing the best set. Such a computation is time-consuming — infeasibly so for more than a few dozen particles. Instead, for each particle in the previous frame, `trackpy` restricts its search of the current frame to a circular region of radius `search_range`, centered on the particle's most likely new position. One supplies an appropriate `search_range` as an arugment to the `link` family of functions, so that for each particle, there will be at most a few candidate features to consider.The `search_range` technique usually speeds up linking, but it does not solve the basic problem. Consider the following (admittedly pathological) example, in which a grid of points in the first frame (large blue dots) mysteriously shifts and loses particles in the second frame (small green dots).<jupyter_code>import trackpy
import numpy as np
import pandas as pd
# change this to %matplotlib notebook for interactive plotting
%matplotlib inline
import matplotlib.pyplot as plt
x0 = np.mgrid[-4:5,-4:5].astype(float)
x0[x0 == 0] = np.nan
x0 += np.random.normal(scale=0.05, size=x0.shape)
pts0 = pd.DataFrame(dict(x=x0[0].flatten(), y=x0[1].flatten(), frame=0)).dropna()

y0 = np.mgrid[-4:4,-4:4] + 0.5
y0[y0 == 0.5] = np.nan
y0 += np.random.normal(scale=0.05, size=y0.shape)
pts1 = pd.DataFrame(dict(x=y0[0].flatten(), y=y0[1].flatten(), frame=1)).dropna()
ax1 = plt.figure(figsize=(6,6)).add_subplot(111)
ax1.plot(pts0.x, pts0.y, 'bo')
ax1.plot(pts1.x, pts1.y, 'g.')
ax1.axis('equal')
ax1.set_xlim([-5, 5])
ax1.set_ylim([-5, 5])
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.10325318246110325, 'fraction_numerical': 0.028288543140028287, 'mean_word_length': 5.27810650887574, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '1', 'dir': 'jupyter-structured-clean-dedup', 'id': '299104', 'license': 'BSD-3-Clause', 'n_tokens_mistral': 679, 'n_tokens_neox': 654, 'n_words': 271, 'path': 'notebooks/subnets.ipynb', 'repo_name': 'nkeim/trackpy-examples', 'size': '2121'}