models.py
3.4 kB · python · 112 lines
1import numpy as np2from copy import deepcopy3from typing import Any, Dict, List, Optional, Tuple4from mrlypy.core.errors import MrlyError56class Node:78 def __init__(self, position: Tuple[float, ...], index: Optional[int] = None):9 self.position = tuple(float(c) for c in position)10 self.index = index1112 @property13 def dim(self) -> int:14 return len(self.position)1516 def __repr__(self) -> str:17 coords = ", ".join(f"{c:g}" for c in self.position)18 return f"Node({coords})"1920class Branch:2122 def __init__(self, parent: int, child: int, radius: float = 1.0):23 self.parent = parent24 self.child = child25 self.radius = float(radius)2627 def __repr__(self) -> str:28 return f"Branch({self.parent}->{self.child}, r={self.radius:g})"2930class Network:3132 def __init__(self, dim: int = 2):33 self._dim = dim34 self.nodes: List[Node] = []35 self.branches: List[Branch] = []3637 @property38 def dim(self) -> int:39 return self._dim4041 # MAIN4243 def add_node(self, position: Tuple[float, ...]) -> int:44 if len(position) != self._dim:45 raise MrlyError(f"Expected {self._dim}D position, got {len(position)}D")46 index = len(self.nodes)47 self.nodes.append(Node(position, index))48 return index4950 def add_branch(self, parent: int, child: int, radius: float = 1.0) -> Branch:51 n = len(self.nodes)52 if not (0 <= parent < n and 0 <= child < n):53 raise MrlyError(f"Branch endpoints out of range: {parent}, {child}")54 branch = Branch(parent, child, radius)55 self.branches.append(branch)56 return branch5758 def copy(self) -> "Network":59 return deepcopy(self)6061 def __repr__(self) -> str:62 return f"Network(dim={self._dim}, nodes={len(self.nodes)}, branches={len(self.branches)})"6364 # DERIVED6566 def positions(self) -> np.ndarray:67 if not self.nodes:68 return np.zeros((0, self._dim))69 return np.array([node.position for node in self.nodes], dtype=float)7071 def edge_array(self) -> np.ndarray:72 if not self.branches:73 return np.zeros((0, 2), dtype=np.int64)74 return np.array([(b.parent, b.child) for b in self.branches], dtype=np.int64)7576 def degree(self) -> np.ndarray:77 deg = np.zeros(len(self.nodes), dtype=np.int64)78 for branch in self.branches:79 deg[branch.parent] += 180 deg[branch.child] += 181 return deg8283 def adjacency(self) -> Dict[int, List[int]]:84 adj: Dict[int, List[int]] = {i: [] for i in range(len(self.nodes))}85 for branch in self.branches:86 adj[branch.parent].append(branch.child)87 adj[branch.child].append(branch.parent)88 return adj8990 # SERIALIZER9192 def to_dict(self) -> Dict[str, Any]:93 return {94 "dim": self._dim,95 "nodes": [list(node.position) for node in self.nodes],96 "branches": [[b.parent, b.child, b.radius] for b in self.branches],97 }9899 @classmethod100 def from_dict(cls, data: Dict[str, Any]) -> "Network":101 network = cls(dim=data["dim"])102 for position in data["nodes"]:103 network.add_node(tuple(position))104 for parent, child, radius in data["branches"]:105 network.add_branch(parent, child, radius)106 return network107108 # CENSUS109110 def census(self) -> Dict[str, float]:111 from . import census112 return census.census(self)