Spaces:
Sleeping
Sleeping
Download graph/BFS.py from arana22/Data-Generator-MVP: direct link, hf CLI and curl.
- Browser
- Download file 5.5 kB
-
https://huggingface.co/spaces/arana22/Data-Generator-MVP/resolve/main/graph/BFS.py
- Command line
-
hf download hf://spaces/arana22/Data-Generator-MVP/graph/BFS.py
-
curl -L -o BFS.py https://huggingface.co/spaces/arana22/Data-Generator-MVP/resolve/main/graph/BFS.py
5.5 kB
| import os | |
| from collections import defaultdict, deque | |
| import requests | |
| import yaml | |
| import pandas as pd | |
| from itertools import combinations | |
| NODE_MODEL_URL = os.getenv("NODE_MODEL_URL", "https://raw.githubusercontent.com/CBIIT/icdc-model-tool/master/model-desc/icdc-model.yml") | |
| def load_yaml_from_url(url: str) -> dict: | |
| if not url: | |
| raise ValueError('Missing URL') | |
| resp = requests.get(url, timeout=60) | |
| resp.raise_for_status() | |
| return yaml.safe_load(resp.text) | |
| def build_graph_from_yaml(node_model): | |
| graph = defaultdict(list) | |
| all_nodes = set() | |
| # add all nodes | |
| for node in node_model.get("Nodes", {}): | |
| all_nodes.add(node) | |
| graph[node] | |
| # build edges | |
| for rel_spec in node_model.get("Relationships", {}).values(): | |
| for end in rel_spec.get("Ends", []): | |
| src = end.get("Src") | |
| dst = end.get("Dst") | |
| if not src or not dst or src == dst: | |
| continue | |
| parent = dst # ✅ correct | |
| child = src # ✅ correct | |
| all_nodes.update([parent, child]) | |
| if child not in graph[parent]: | |
| graph[parent].append(child) | |
| graph[child] # ensure child exists | |
| return dict(graph), all_nodes | |
| def bfs_levels(graph, root): | |
| """ | |
| Return nodes level-by-level as a 2D array: | |
| [ | |
| [root], | |
| [level_1_nodes], | |
| [level_2_nodes], | |
| ... | |
| ] | |
| """ | |
| visited = set([root]) | |
| queue = deque([root]) | |
| levels = [] | |
| while queue: | |
| level_size = len(queue) | |
| level = [] | |
| for _ in range(level_size): | |
| node = queue.popleft() | |
| level.append(node) | |
| for nxt in graph.get(node, []): | |
| if nxt not in visited: | |
| visited.add(nxt) | |
| queue.append(nxt) | |
| levels.append(level) | |
| return levels | |
| def bfs_parent_map(graph, root): | |
| # output | |
| # program -> ['study'] | |
| # study -> ['study_arm', 'case', 'cohort', 'human_relevance', 'file', 'consent_group'] | |
| # consent_group -> ['case'] | |
| # human_relevance -> [] | |
| # study_site -> ['study'] | |
| # study_arm -> ['cohort', 'case'] | |
| # cohort -> ['case'] | |
| # case -> ['enrollment', 'demographic', 'diagnosis', 'cycle', 'sample', 'file', 'visit', 'adverse_event'] | |
| visited = set([root]) | |
| queue = deque([root]) | |
| parent = {root: None} | |
| while queue: | |
| node = queue.popleft() | |
| for child in graph.get(node, []): | |
| if child not in visited: | |
| visited.add(child) | |
| parent[child] = node | |
| queue.append(child) | |
| return parent | |
| def reconstruct_path(parent_map, node): | |
| path = [] | |
| cur = node | |
| while cur is not None: | |
| path.append(cur) | |
| cur = parent_map.get(cur) | |
| return path[::-1] | |
| def get_paths_for_nodes(parent_map, nodes): | |
| # output | |
| # ['program', 'study', 'file'] | |
| # ['program', 'study', 'case', 'sample'] | |
| # ['program', 'study', 'case', 'visit'] | |
| paths = {} | |
| for node in nodes: | |
| if node in parent_map: | |
| paths[node] = reconstruct_path(parent_map, node) | |
| else: | |
| paths[node] = None # node not reachable | |
| return paths | |
| def paths_dict_to_edges_ordered(paths_by_node): | |
| # used for cross node analysis | |
| # [('program', 'study'), ('study', 'file'), ('study', 'case'), ('case', 'sample'), ('case', 'visit')] | |
| edges = [] | |
| seen = set() | |
| for end_node, path in paths_by_node.items(): | |
| if not isinstance(path, list): | |
| continue | |
| for i in range(len(path) - 1): | |
| edge = (path[i], path[i + 1]) | |
| if edge not in seen: | |
| seen.add(edge) | |
| edges.append(edge) | |
| return edges | |
| def graph_to_df(graph): | |
| rows = [] | |
| for parent, children in graph.items(): | |
| rows.append({ | |
| "parent": parent, | |
| "child": children | |
| }) | |
| return pd.DataFrame(rows) | |
| def get_edges_for_nodes(nodes: list[str]): | |
| if len(nodes) > 1: | |
| node_model = load_yaml_from_url(NODE_MODEL_URL) | |
| graph, all_nodes = build_graph_from_yaml(node_model) | |
| parent_map = bfs_parent_map(graph, "program") | |
| paths = get_paths_for_nodes(parent_map, nodes) | |
| edges = paths_dict_to_edges_ordered(paths) | |
| edges_df = pd.DataFrame(edges, columns=["parent", "child"]) | |
| # print(edges) | |
| return graph, paths, edges, edges_df, "" | |
| else: | |
| return None, None, None, None, f"Provide nodes" | |
| def build_graph(edges): | |
| graph = defaultdict(list) | |
| for parent, child in edges: | |
| if child not in graph[parent]: | |
| graph[parent].append(child) | |
| if parent not in graph: | |
| graph[parent] = graph[parent] | |
| return dict(graph) | |
| def shortest_path(graph, start, end): | |
| if start == end: | |
| return [start] | |
| queue = deque([[start]]) | |
| visited = {start} | |
| while queue: | |
| path = queue.popleft() | |
| node = path[-1] | |
| for neighbor in graph.get(node, []): | |
| if neighbor in visited: | |
| continue | |
| new_path = path + [neighbor] | |
| if neighbor == end: | |
| return new_path | |
| visited.add(neighbor) | |
| queue.append(new_path) | |
| return None | |
| def find_selected_edges(edges, selected_nodes): | |
| selected_set = set(selected_nodes) | |
| return [edge for edge in edges if edge[0] in selected_set and edge[1] in selected_set] | |