<gh_stars>0
import numpy as np
from tqdm import tqdm
import graph.total_size as total


class Graph:
    def __init__(
        self,
        cover_radius: float,
        number_of_points: int,
        exploration_factor=1.5,
        intersection_weight=1000,
        points=None,
    ) -> None:
        # graphs attributes
        self.points = points
        self.adj_neighbour = None

        # settings for the graph
        self.number_of_points = number_of_points
        self.cover_radius = cover_radius
        self.exploration_factor = exploration_factor

        # attributes for the weighted intersection search
        self.intersection_points = None
        self.mid_points = None
        self.intersection_weight = intersection_weight
        self.mid_neg_weight = -intersection_weight
        pass

    def __len__(self) -> int:
        return int(self.number_of_points)

    def __sizeof__(self) -> int:
        return total.total_size(self.points) + total.total_size(self.adj_neighbour)

    ##############################
    ##############################
    ##############################
    # Intersection Part
    ##############################
    ##############################

    def add_intersection_point(self, p1, p2):
        if self.intersection_points is None:
            self.intersection_points = p1
        else:
            self.intersection_points = np.vstack(
                (self.intersection_points, p1))
        self.intersection_points = np.vstack(
            (self.intersection_points, p2))

    def add_mid_point(self, m1, m2):
        """m1 und m2 sind die Mittelpunkte, zwischen denen der Mittelpunkt gelegt werden soll"""
        mid = m1 + m2
        mid = mid / np.linalg.norm(mid)

        if self.mid_points is None:
            self.mid_points = np.array(mid)
        else:
            self.mid_points = np.vstack((self.mid_points, mid))

    def count_intersections_next_to(self, label):
        if self.intersection_points is None:
            return 0
        vec = self.points[label][3:6]
        vec = np.matmul(self.intersection_points, np.transpose(vec))
        dist = np.arccos(vec) < self.cover_radius

        return np.sum(dist.astype(np.int8))

    def count_mid_next_to(self, label):
        if self.mid_points is None:
            return 0
        vec = self.points[label][3:6]
        vec = np.matmul(self.mid_points, np.transpose(vec))
        dist = np.arccos(vec) < self.cover_radius

        return np.sum(dist.astype(np.int8))

    def delect_intersects(self, label):
        if self.intersection_points is None:
            return
        vec = self.points[label][3:6]
        vec = np.matmul(self.intersection_points, np.transpose(vec))
        dist = np.arccos(vec) < self.cover_radius

        indices = np.where(dist == True)[0]
        self.intersection_points = np.delete(
            self.intersection_points, indices, axis=0
        )

        pass

    ##############################
    ##############################
    ##############################
    # Updating and Getting Vectors
    ##############################
    ##############################

    def update_all_neighbours(self, steps: int = 1) -> None:
        stepsize = np.linspace(num=steps + 1, start=0, stop=len(self.points)).astype(
            int
        )

        arr = None
        other = self.points[:, 3:6]
        for i in tqdm(range(len(stepsize) - 1)):
            a, b = stepsize[i], stepsize[i + 1]
            cartesian_slice = self.points[a:b, 3:6]

            matrix = np.matmul(cartesian_slice, np.transpose(other))

            # ugly bugfix, da die diagonale auf 1 zu setzten nicht so trivial ist (diagonale nach dem zweiten slice fängt in der mitte an)
            matrix[np.where(matrix > 1)] = 1

            dist = np.arccos(matrix)
            # transform to bit vector
            byte_matrix = dist < self.cover_radius
            # -1 zeigt an, dass es vectorweise geht und nicht erst gefattend wird
            packed = np.packbits(byte_matrix, axis=-1)

            # zur temporären Lösung hinzufügen
            if arr is None:
                arr = packed
            else:
                arr = np.vstack((arr, packed))

            pass
        self.adj_neighbour = arr
        pass

    ##############################
    ##############################
    ########## Getters ###########
    ##############################
    ##############################

    def get_extension_and_reach(self, label):
        d = self._get_distance_vector_label(label)
        byte_extension = d < self.exploration_factor * self.cover_radius
        byte_reach = d < 2 * self.cover_radius
        return byte_extension, byte_reach

    def _get_distance_vector_label(self, label) -> np.array:
        cartesian = self.points[:, 3:6]
        # ermöglicht auch beliebig distanzvektoren zu generieren
        other = cartesian[label]
        vec = np.matmul(cartesian, np.transpose(other))

        # verhindert, dass arccos bei d(label, label) aufgrund Rundungsfehler fehlschlagen würde.
        vec[label] = 1

        return np.arccos(vec)

    def get_neighbour_vector(self, label) -> np.array:
        return np.unpackbits(self.adj_neighbour[label])[0:self.number_of_points]


pass

import numpy as np
from tqdm import tqdm
import graph.total_size as total


class Graph:
    def __init__(
        self,
        cover_radius: float,
        number_of_points: int,
        exploration_factor=1.5,
        intersection_weight=1000,
        points=None,
    ) -> None:
        # graphs attributes
        self.points = points
        self.adj_neighbour = None

        # settings for the graph
        self.number_of_points = number_of_points
        self.cover_radius = cover_radius
        self.exploration_factor = exploration_factor

        # attributes for the weighted intersection search
        self.intersection_points = None
        self.mid_points = None
        self.intersection_weight = intersection_weight
        self.mid_neg_weight = -intersection_weight
        pass

    def __len__(self) -> int:
        return int(self.number_of_points)

    def __sizeof__(self) -> int:
        return total.total_size(self.points) + total.total_size(self.adj_neighbour)

    ##############################
    ##############################
    ##############################
    # Intersection Part
    ##############################
    ##############################

    def add_intersection_point(self, p1, p2):
        if self.intersection_points is None:
            self.intersection_points = p1
        else:
            self.intersection_points = np.vstack(
                (self.intersection_points, p1))
        self.intersection_points = np.vstack(
            (self.intersection_points, p2))

    def add_mid_point(self, m1, m2):
        """m1 und m2 sind die Mittelpunkte, zwischen denen der Mittelpunkt gelegt werden soll"""
        mid = m1 + m2
        mid = mid / np.linalg.norm(mid)

        if self.mid_points is None:
            self.mid_points = np.array(mid)
        else:
            self.mid_points = np.vstack((self.mid_points, mid))

    def count_intersections_next_to(self, label):
        if self.intersection_points is None:
            return 0
        vec = self.points[label][3:6]
        vec = np.matmul(self.intersection_points, np.transpose(vec))
        dist = np.arccos(vec) < self.cover_radius

        return np.sum(dist.astype(np.int8))

    def count_mid_next_to(self, label):
        if self.mid_points is None:
            return 0
        vec = self.points[label][3:6]
        vec = np.matmul(self.mid_points, np.transpose(vec))
        dist = np.arccos(vec) < self.cover_radius

        return np.sum(dist.astype(np.int8))

    def delect_intersects(self, label):
        if self.intersection_points is None:
            return
        vec = self.points[label][3:6]
        vec = np.matmul(self.intersection_points, np.transpose(vec))
        dist = np.arccos(vec) < self.cover_radius

        indices = np.where(dist == True)[0]
        self.intersection_points = np.delete(
            self.intersection_points, indices, axis=0
        )

        pass

    ##############################
    ##############################
    ##############################
    # Updating and Getting Vectors
    ##############################
    ##############################

    def update_all_neighbours(self, steps: int = 1) -> None:
        stepsize = np.linspace(num=steps + 1, start=0, stop=len(self.points)).astype(
            int
        )

        arr = None
        other = self.points[:, 3:6]
        for i in tqdm(range(len(stepsize) - 1)):
            a, b = stepsize[i], stepsize[i + 1]
            cartesian_slice = self.points[a:b, 3:6]

            matrix = np.matmul(cartesian_slice, np.transpose(other))

            # ugly bugfix, da die diagonale auf 1 zu setzten nicht so trivial ist (diagonale nach dem zweiten slice fängt in der mitte an)
            matrix[np.where(matrix > 1)] = 1

            dist = np.arccos(matrix)
            # transform to bit vector
            byte_matrix = dist < self.cover_radius
            # -1 zeigt an, dass es vectorweise geht und nicht erst gefattend wird
            packed = np.packbits(byte_matrix, axis=-1)

            # zur temporären Lösung hinzufügen
            if arr is None:
                arr = packed
            else:
                arr = np.vstack((arr, packed))

            pass
        self.adj_neighbour = arr
        pass

    ##############################
    ##############################
    ########## Getters ###########
    ##############################
    ##############################

    def get_extension_and_reach(self, label):
        d = self._get_distance_vector_label(label)
        byte_extension = d < self.exploration_factor * self.cover_radius
        byte_reach = d < 2 * self.cover_radius
        return byte_extension, byte_reach

    def _get_distance_vector_label(self, label) -> np.array:
        cartesian = self.points[:, 3:6]
        # ermöglicht auch beliebig distanzvektoren zu generieren
        other = cartesian[label]
        vec = np.matmul(cartesian, np.transpose(other))

        # verhindert, dass arccos bei d(label, label) aufgrund Rundungsfehler fehlschlagen würde.
        vec[label] = 1

        return np.arccos(vec)

    def get_neighbour_vector(self, label) -> np.array:
        return np.unpackbits(self.adj_neighbour[label])[0:self.number_of_points]


pass
# graphs attributes # settings for the graph # attributes for the weighted intersection search ############################## ############################## ############################## # Intersection Part ############################## ############################## m1 und m2 sind die Mittelpunkte, zwischen denen der Mittelpunkt gelegt werden soll ############################## ############################## ############################## # Updating and Getting Vectors ############################## ############################## # ugly bugfix, da die diagonale auf 1 zu setzten nicht so trivial ist (diagonale nach dem zweiten slice fängt in der mitte an) # transform to bit vector # -1 zeigt an, dass es vectorweise geht und nicht erst gefattend wird # zur temporären Lösung hinzufügen ############################## ############################## ########## Getters ########### ############################## ############################## # ermöglicht auch beliebig distanzvektoren zu generieren # verhindert, dass arccos bei d(label, label) aufgrund Rundungsfehler fehlschlagen würde.