Source code for mapof.core.features.stability


import numpy as np

from mapof.core.features.common import extract_selected_coordinates
from mapof.core.objects.Experiment import Experiment


def calculate_stability(
        experiment: Experiment,
        election_ids: list[str] = None,
        rotate_to_match: bool = True
) -> dict:

    if election_ids is None:
        election_ids = list(experiment.distances.keys())

    coordinates = []

    for coordinate_dict in experiment.coordinates_lists.values():
        coordinates.append(extract_selected_coordinates(coordinate_dict, election_ids))

    if rotate_to_match:
        for i in range(1, len(coordinates)):
            coordinates[i] = rotate_coordinates_to_match(coordinates[i], coordinates[0])

    coordinates_differences = []

    for i in range(len(coordinates)):
        for j in range(i + 1, len(coordinates)):
            coordinates_differences.append(np.linalg.norm(coordinates[i] - coordinates[j], axis=1))

    coordinates_differences = np.array(coordinates_differences)
    differences_mean = np.mean(coordinates_differences, axis=0)

    return {
        election: differences_mean[i] for i, election in enumerate(election_ids)
    }


[docs] def rotate_via_numpy(coordinates, radians): """Use numpy to build a rotation matrix and take the dot product.""" c, s = np.cos(radians), np.sin(radians) j = np.matrix([[c, s], [-s, c]]) m = np.dot(coordinates, j) return m
def rotate_coordinates_to_match(coordinates_to_rotate, coordinates_to_match): assert coordinates_to_rotate.shape == coordinates_to_match.shape num_rows, num_cols = coordinates_to_rotate.shape if num_cols != 2: raise Exception(f"matrix is not Nx2, it is {num_rows}x{num_cols}") coordinates_to_rotate = np.transpose(coordinates_to_rotate, [1, 0]) coordinates_to_match = np.transpose(coordinates_to_match, [1, 0]) centroid_a = np.mean(coordinates_to_rotate, axis=1) centroid_b = np.mean(coordinates_to_match, axis=1) # ensure centroids are 1x2 centroid_a = centroid_a.reshape(-1, 1) centroid_b = centroid_b.reshape(-1, 1) # subtract mean Am = coordinates_to_rotate - centroid_a Bm = coordinates_to_match - centroid_b H = Am @ np.transpose(Bm) U, S, Vt = np.linalg.svd(H) R = Vt.T @ U.T # special reflection case if np.linalg.det(R) < 0: print("det(R) < R, reflection detected!, correcting for it ...") Vt[2, :] *= -1 R = Vt.T @ U.T t = -R @ centroid_a + centroid_b return ((R @ coordinates_to_rotate) + t).T