""" Synthetic random tree, for validating the embedding/training/evaluation pipeline WITHOUT needing live PBDB network access (blocked in this development sandbox). Provenance is explicitly "SYNTHETIC_TREE" — never to be confused with real PBDB taxonomy data, and never silently substituted for it (same discipline as get_synthetic_dataset() in data_real.py: explicit opt-in only, clearly labeled). Construction: a random recursive tree — node i (for i >= 1) attaches to a uniformly random existing node in {0, ..., i-1}. This guarantees a single connected tree with no cycles, node 0 as root, and is a standard, well-understood random tree model (not an arbitrary ad hoc generator). """ from __future__ import annotations from typing import Dict, List, Tuple import torch from .data_pbdb_taxonomy import TaxonomyEdgeDataset def generate_synthetic_tree( n_nodes: int = 300, seed: int = 0, ) -> Tuple[List[Tuple[str, str]], Dict[str, Dict[str, int]]]: gen = torch.Generator().manual_seed(seed) edges: List[Tuple[str, str]] = [] node_attrs: Dict[str, Dict[str, int]] = {"n0": {"depth": 0}} depth = [0] * n_nodes for i in range(1, n_nodes): parent = int(torch.randint(0, i, (1,), generator=gen).item()) edges.append((f"n{i}", f"n{parent}")) depth[i] = depth[parent] + 1 node_attrs[f"n{i}"] = {"depth": depth[i]} return edges, node_attrs def generate_balanced_tree( branching_factor: int = 3, depth: int = 6, ) -> Tuple[List[Tuple[str, str]], Dict[str, Dict[str, int]]]: edges: List[Tuple[str, str]] = [] node_attrs: Dict[str, Dict[str, int]] = {"n0": {"depth": 0}} frontier = ["n0"] counter = 1 for d in range(1, depth + 1): next_frontier = [] for parent in frontier: for _ in range(branching_factor): name = f"n{counter}" counter += 1 edges.append((name, parent)) node_attrs[name] = {"depth": d} next_frontier.append(name) frontier = next_frontier return edges, node_attrs def get_synthetic_tree_dataset(n_nodes: int = 300, seed: int = 0, tree_type: str = "random_recursive", branching_factor: int = 3, depth: int = 6): if tree_type == "balanced": edges, node_attrs = generate_balanced_tree(branching_factor=branching_factor, depth=depth) else: edges, node_attrs = generate_synthetic_tree(n_nodes=n_nodes, seed=seed) dataset = TaxonomyEdgeDataset(edges, node_attrs) node_depths_by_idx = { dataset.node_to_idx[name]: attrs["depth"] for name, attrs in node_attrs.items() } meta = { "num_nodes": dataset.num_nodes, "num_edges": len(edges), "seed": seed, "tree_type": tree_type, "node_depths_by_idx": node_depths_by_idx, } print(f"[data] SYNTHETIC_TREE ({tree_type}): {dataset.num_nodes} nodes, {len(edges)} edges " f"(seed={seed}) — NOT real PBDB data") return dataset, meta, "SYNTHETIC_TREE"