pixi 0.15.2

A package management and workflow tool
Documentation
import rerun as rr
import networkx as nx
import yaml
import numpy as np
import hashlib
import sys

# Give relative path or default to local pixi.lock
lockfile_path = sys.argv[1] if len(sys.argv) > 1 else 'pixi.lock'

with open(lockfile_path, 'r') as file:
    lockfile_data = yaml.safe_load(file)

package_data = lockfile_data['package']
package_names = [package['name'] for package in package_data]

graph = nx.DiGraph()
for package in package_data:
    package_name = package['name']
    dependencies = package.get('dependencies', [])
    graph.add_node(package_name)
    for i, dep in enumerate(dependencies):
        graph.add_edge(package_name, dep.split(" ")[0])

rr.init("fdg", spawn=True)
rr.connect()

def hash_string_to_int(string):
    return int(hashlib.sha256(string.encode('utf-8')).hexdigest(), 16) % (10 ** 8)


# Memoization dictionary
color_cache = {}


# Function to get color
def get_color_for_node(node):
    if node not in color_cache:
        np.random.seed(hash_string_to_int(node))
        color_cache[node] = np.random.rand(3)  # Generate and store color
    return color_cache[node]


def apply_forces_and_log(graph, pos):
    damping = 0.9
    max_force = 1
    degree_scale = 2  # Scale factor for degree-based forces
    dist_scale = 0.5

    iterations = 1000
    repulsive_force = 0.01
    attractive_force = 0.005

    for iteration in range(iterations):
        force = {node: np.zeros(3) for node in graph}

        # Degree-based repulsive forces
        for i, node1 in enumerate(graph):
            for node2 in list(graph)[i + 1:]:
                diff = pos[node1] - pos[node2]
                dist = (np.linalg.norm(diff) + 1e-9) * dist_scale
                degree_factor = (
                        (graph.degree(node1) + graph.degree(node2)) * degree_scale)
                repel = repulsive_force * degree_factor / dist ** 2
                force_vector = repel * diff  # / dist
                force[node1] += np.clip(force_vector, -max_force, max_force)
                force[node2] -= np.clip(force_vector, -max_force, max_force)

        # Degree-based attractive forces
        for edge in graph.edges():
            u, v = edge
            diff = pos[u] - pos[v]
            dist = dist = (np.linalg.norm(diff) + 1e-9) * dist_scale
            if dist > 0:
                degree_factor = (graph.degree(u) + graph.degree(v)) * degree_scale
                attract = (attractive_force * dist ** 2) / degree_factor
                force[u] -= attract * diff / dist
                force[v] += attract * diff / dist

        # Update positions with damping
        for node in graph:
            pos[node] += force[node] * damping
            position = np.array(pos[node])
            color = get_color_for_node(node)  # Retrieve color, memoized
            rr.log(f"graph_nodes/{node}",
                   rr.Points3D([position],
                               colors=[color],
                               radii=max(graph.degree(node) / 20, 0.5)),
                   rr.AnyValues(node))

        edges_array = np.array([[pos[u], pos[v]] for u, v in graph.edges()])

        # Log the edges array
        rr.log("graph_nodes/graph_edges",
               rr.LineStrips3D(edges_array, radii=0.02, colors=[1, 1, 1, 0.1]))

    return pos


# Identify the node with the highest degree
central_node = max(graph.degree, key=lambda x: x[1])[0]

# Initial positions with the central node at the center
initial_pos = nx.spring_layout(graph, dim=3)
initial_pos[central_node] = np.array([0.5, 0.5, 0.5])  # Center position

# Apply the force-directed simulation
final_pos = apply_forces_and_log(graph, initial_pos)