census.py

2.6 kB · python · 94 lines

1import numpy as np2from typing import Dict, TYPE_CHECKING3from mrlypy.core.errors import MrlyError45if TYPE_CHECKING:6    from .models import Network78# PRIMITIVES910def branches(network: "Network") -> int:11    return len(network.branches)1213def nodes(network: "Network") -> int:14    return len(network.nodes)1516def total_length(network: "Network") -> float:17    positions = network.positions()18    total = 0.019    for branch in network.branches:20        a = positions[branch.parent]21        b = positions[branch.child]22        total += float(np.linalg.norm(a - b))23    return total2425def tips(network: "Network") -> int:26    degree = network.degree()27    if len(degree) == 0:28        return 029    return int((degree == 1).sum())3031def junctions(network: "Network") -> int:32    degree = network.degree()33    if len(degree) == 0:34        return 035    return int((degree >= 3).sum())3637def components(network: "Network") -> int:38    n = len(network.nodes)39    if n == 0:40        return 041    adjacency = network.adjacency()42    seen = np.zeros(n, dtype=bool)43    count = 044    for start in range(n):45        if seen[start]:46            continue47        count += 148        stack = [start]49        seen[start] = True50        while stack:51            current = stack.pop()52            for neighbor in adjacency[current]:53                if not seen[neighbor]:54                    seen[neighbor] = True55                    stack.append(neighbor)56    return count5758# FRACTAL DIMENSION - BOX COUNTING ON NODE POSITIONS5960def fractal_dimension(network: "Network", samples: int = 12) -> float:61    positions = network.positions()62    if len(positions) < 2:63        return 0.064    mins = positions.min(axis=0)65    maxs = positions.max(axis=0)66    extent = float((maxs - mins).max())67    if extent == 0:68        return 0.069    normalized = (positions - mins) / extent70    sizes = np.geomspace(1.0, 1.0 / 256.0, samples)71    counts = []72    used = []73    for size in sizes:74        keys = np.floor(normalized / size).astype(np.int64)75        unique = {tuple(row) for row in keys}76        counts.append(len(unique))77        used.append(size)78    log_inv_size = np.log(1.0 / np.array(used))79    log_count = np.log(np.array(counts))80    slope = np.polyfit(log_inv_size, log_count, 1)[0]81    return float(slope)8283# PUBLIC8485def census(network: "Network") -> Dict[str, float]:86    return {87        "nodes": nodes(network),88        "branches": branches(network),89        "tips": tips(network),90        "junctions": junctions(network),91        "components": components(network),92        "total_length": round(total_length(network), 6),93        "fractal_dimension": round(fractal_dimension(network), 4),94    }