I read through the numpy
code for this function but it is not obvious to me what determines the ordering or if it could be adjusted to yield more "consistent" results, so I think it would be easiest to just adjust the values after you get them.
A potentially quick and easy solution would be to just regress a series of points from your assessed "together" group and then grab whichever next point best matches that regression. Here is a sample implementation you can start from which shows pretty good results on a test set I came up with:
import numpy as np
import matplotlib.pyplot as plt
import itertools
PROJECTION_WINDOW = 4
def group_vals(xvals: np.ndarray, yvals: np.ndarray) -> np.ndarray:
# Pre-initialize the array to the same size
out_array = np.zeros_like(yvals)
# Pre-set the first vector as a starting point
out_array[0] = yvals[0]
# Iterate though each vector
for pos in range(1, yvals.shape[0]):
if pos < PROJECTION_WINDOW:
# If we don't have enough values to project, use the previous
# value as the projected value to match
projections = out_array[pos - 1]
else:
# If enough values have been collected to project the next one,
# draw a line through the last PROJECTION_WINDOW values and
# predict the next x value using that regression
# Repeat this for each position in the vector
# https://stackoverflow.com/a/6148315
projections = np.array([
np.poly1d(
np.polyfit(
xvals[pos - PROJECTION_WINDOW: pos],
out_array[pos - PROJECTION_WINDOW: pos, col],
1
)
)(xvals[pos])
for col in range(out_array.shape[1])
])
# Find all possible combinations of next point to previous point
candidates = itertools.permutations(yvals[pos])
# Capture the candidate with the best score
best_candidate = None
# Capture the best candidate's score
best_score = None
# Check each candidate to find out which has the lowest/best score
for candidate in candidates:
# Calculate the score as the square of the sum of distances
# between the projected value and the candidate value
candidate_score = np.sum(np.abs(projections - candidate)) ** 2
# If this was the first pass, then the one we checked is
# the best so far
# If this was a subsequent pass, check the new score
# against the previous best
if best_score is None or candidate_score < best_score:
best_candidate = candidate
best_score = candidate_score
# Whichever scored the best, keep
out_array[pos] = best_candidate
return out_array
def _main():
def get_eigens(f, b):
array = np.array([
[1.0, 0.2, 0.3],
[0.4, 0.5, 0.6],
[0.7, f, b]
], dtype=float)
return np.linalg.eig(array).eigenvalues
f_interesting = [-3, -2, -1, 0.1975, 0.222, 1.5]
for f in f_interesting:
count = 256
res = []
b_vals = np.linspace(-2 * np.pi, 2 * np.pi, count)
for b in b_vals:
res.append(get_eigens(f, b))
res = np.abs(np.array(res))
res_sorted = group_vals(b_vals, res)
fig, axs = plt.subplots(2, 1)
axs[0].set_title(f'f = {f}\nOriginal')
axs[0].plot(b_vals, res[:, 0])
axs[0].plot(b_vals, res[:, 1])
axs[0].plot(b_vals, res[:, 2])
axs[1].set_title('Adjusted batch', pad=20)
axs[1].plot(b_vals, res_sorted[:, 0])
axs[1].plot(b_vals, res_sorted[:, 1])
axs[1].plot(b_vals, res_sorted[:, 2])
fig.tight_layout()
plt.show()
if __name__ == '__main__':
_main()
Here are the plots it generates, which show the same features you didn't like in your original plots and corrects them all about as well as I could by hand I think:
Let me know if you have any questions.