# ๐Ÿ•ธ๏ธ Graph Pipeline Phase 2: ์œ„์ƒ ๋ชจ๋ธ๋ง (Topology Modeling) ์ด ๋ฌธ์„œ๋Š” P&ID Graph Pipeline์˜ ๋‘ ๋ฒˆ์งธ ๋‹จ๊ณ„์ธ **์œ„์ƒ ๋ชจ๋ธ๋ง**์˜ ์ƒ์„ธ ๊ตฌํ˜„ ๊ณ„ํš์„ ๋‹ค๋ฃน๋‹ˆ๋‹ค. 1๋‹จ๊ณ„์—์„œ ์ถ”์ถœํ•œ ๊ธฐํ•˜ํ•™์  ๊ฐ์ฒด(์ขŒํ‘œ, BBox)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ, ์„ค๋น„ ๊ฐ„์˜ **์—ฐ๊ฒฐ์„ฑ(Connectivity)**๊ณผ **ํ๋ฆ„(Flow)**์„ ์ •์˜ํ•˜๋Š” ์ง€์‹ ๊ทธ๋ž˜ํ”„(Knowledge Graph)๋ฅผ ๊ตฌ์ถ•ํ•˜๋Š” ๊ฒƒ์ด ๋ชฉํ‘œ์ž…๋‹ˆ๋‹ค. --- ## ๐Ÿ“ฆ 1. ํ•„์ˆ˜ ํŒจํ‚ค์ง€ ๋ฐ ํ™˜๊ฒฝ ์„ค์ • ### 1.1 Python ํŒจํ‚ค์ง€ | ํŒจํ‚ค์ง€ | ์šฉ๋„ | ๋น„๊ณ  | |---|---|---| | `networkx` | ๊ทธ๋ž˜ํ”„ ๋ฐ์ดํ„ฐ ๊ตฌ์กฐ ์ƒ์„ฑ ๋ฐ ์•Œ๊ณ ๋ฆฌ์ฆ˜ ๋ถ„์„ | ํ•ต์‹ฌ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ | | `shapely` | ๊ฐ์ฒด ๊ฐ„ ๊ฑฐ๋ฆฌ ๊ณ„์‚ฐ ๋ฐ ํฌํ•จ ๊ด€๊ณ„ ๋ถ„์„ | 1๋‹จ๊ณ„์™€ ์—ฐ๊ณ„ | | `scikit-learn` | (์„ ํƒ) KD-Tree๋ฅผ ์ด์šฉํ•œ ๊ณ ์† ๊ทผ์ ‘ ์ด์›ƒ ๊ฒ€์ƒ‰ | ๋Œ€๊ทœ๋ชจ ๋„๋ฉด ์ตœ์ ํ™” | | `matplotlib` | ์ƒ์„ฑ๋œ ๊ทธ๋ž˜ํ”„์˜ ์œ„์ƒ ๊ตฌ์กฐ ์‹œ๊ฐํ™” ๊ฒ€์ฆ | ๋””๋ฒ„๊น…์šฉ | ### 1.2 ์„ค์น˜ ๋ช…๋ น์–ด ```bash pip install networkx shapely scikit-learn matplotlib ``` --- ## ๐Ÿ“ 2. ์ƒ์„ธ ์„ค๊ณ„ ๊ตฌ์กฐ ### 2.1 ๊ทธ๋ž˜ํ”„ ์ •์˜ (Graph Definition) * **๋…ธ๋“œ (Nodes):** * `Equipment`: ํŽŒํ”„, ํƒฑํฌ, ์—ด๊ตํ™˜๊ธฐ ๋“ฑ (์†์„ฑ: ID, ํƒ€์ž…, BBox) * `Instrument`: ์ „์†ก๊ธฐ, ๋ฐธ๋ธŒ, ๊ฒŒ์ด์ง€ ๋“ฑ (์†์„ฑ: ID, ํƒ€์ž…, BBox) * `Tag`: ํ…์ŠคํŠธ ๊ธฐ๋ฐ˜ ํƒœ๊ทธ (์†์„ฑ: TagName, Value) * **์—ฃ์ง€ (Edges):** * `Pipe`: ์„ค๋น„-์„ค๋น„, ์„ค๋น„-๊ณ„๊ธฐ ๊ฐ„์˜ ๋ฌผ๋ฆฌ์  ์—ฐ๊ฒฐ (์†์„ฑ: LineNumber, ๋ฐฉํ–ฅ์„ฑ) * `Association`: ํƒœ๊ทธ-์„ค๋น„ ๊ฐ„์˜ ๋…ผ๋ฆฌ์  ์—ฐ๊ฒฐ (์†์„ฑ: ๊ด€๊ณ„ ํƒ€์ž… - ์˜ˆ: 'belongs_to') ### 2.2 ์œ„์ƒ ์ถ”๋ก  ๋กœ์ง (Topology Inference) 1. **ํƒœ๊ทธ-์„ค๋น„ ๊ฒฐํ•ฉ (Tag-to-Entity Binding):** * ํƒœ๊ทธ ํ…์ŠคํŠธ์˜ BBox์™€ ๊ฐ€์žฅ ๊ฐ€๊นŒ์šด ์‹ฌ๋ณผ(Equipment/Instrument)์„ ์ฐพ์•„ `Association` ์—ฃ์ง€๋ฅผ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. 2. **๋ฐฐ๊ด€ ์—ฐ๊ฒฐ์„ฑ ๋ถ„์„ (Line Connectivity):** * `LINE` ๋˜๋Š” `POLYLINE`์˜ ๋์ ์ด ํŠน์ • ์„ค๋น„์˜ BBox ๋‚ด๋ถ€์— ์žˆ๊ฑฐ๋‚˜ ์ž„๊ณ„ ๊ฑฐ๋ฆฌ($\epsilon$) ์ด๋‚ด์— ์žˆ์œผ๋ฉด ๋‘ ๋…ธ๋“œ๋ฅผ `Pipe` ์—ฃ์ง€๋กœ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค. 3. **ํ๋ฆ„ ๋ฐฉํ–ฅ์„ฑ ๋ถ€์—ฌ (Flow Direction):** * ํ™”์‚ดํ‘œ ์‹ฌ๋ณผ์˜ ๋ฐฉํ–ฅ ๋˜๋Š” ๊ณต์ • ํ๋ฆ„ ๊ทœ์น™์„ ๋ถ„์„ํ•˜์—ฌ ์—ฃ์ง€์— `source` $\rightarrow$ `target` ๋ฐฉํ–ฅ์„ ์„ค์ •ํ•ฉ๋‹ˆ๋‹ค. --- ## ๐Ÿ’ป 3. ์‹ค์ œ ๊ตฌํ˜„ ์ฝ”๋”ฉ ๊ฐ€์ด๋“œ (Example) ### 3.1 ๊ทธ๋ž˜ํ”„ ๊ตฌ์ถ• ํ•ต์‹ฌ ์ฝ”๋“œ ```python import networkx as nx from shapely.geometry import box, Point class PidTopologyBuilder: def __init__(self, geometric_data): self.data = geometric_data # Phase 1์—์„œ ์ถ”์ถœ๋œ JSON ๋ฐ์ดํ„ฐ self.G = nx.DiGraph() # ๋ฐฉํ–ฅ์„ฑ ๊ทธ๋ž˜ํ”„ ์ƒ์„ฑ def build_graph(self): # 1. ๋ชจ๋“  ๊ฐ์ฒด๋ฅผ ๋…ธ๋“œ๋กœ ์ถ”๊ฐ€ for item in self.data: self.G.add_node(item['id'], type=item['type'], bbox=box(*item['bbox'].values()), value=item.get('value')) # 2. ํƒœ๊ทธ-์„ค๋น„ ๋…ผ๋ฆฌ์  ์—ฐ๊ฒฐ (Association) tags = [n for n, d in self.G.nodes(data=True) if d['type'] == 'TEXT'] equipments = [n for n, d in self.G.nodes(data=True) if d['type'] != 'TEXT'] for tag in tags: best_match = self._find_nearest_equipment(tag, equipments) if best_match: self.G.add_edge(tag, best_match, relation='associated_with') # 3. ๋ฐฐ๊ด€ ๊ธฐ๋ฐ˜ ๋ฌผ๋ฆฌ์  ์—ฐ๊ฒฐ (Pipe) lines = [n for n, d in self.G.nodes(data=True) if d['type'] in ['LINE', 'POLYLINE']] for line in lines: connected_nodes = self._find_connected_nodes(line, equipments) if len(connected_nodes) >= 2: # ๋ผ์ธ์„ ํ†ตํ•ด ์—ฐ๊ฒฐ๋œ ๋‘ ์„ค๋น„ ๊ฐ„ ์—ฃ์ง€ ์ƒ์„ฑ self.G.add_edge(connected_nodes[0], connected_nodes[1], relation='pipe') def _find_nearest_equipment(self, tag_id, equipment_ids): tag_bbox = self.G.nodes[tag_id]['bbox'] min_dist = float('inf') nearest = None for eq_id in equipment_ids: eq_bbox = self.G.nodes[eq_id]['bbox'] dist = tag_bbox.distance(eq_bbox) if dist < min_dist: min_dist = dist nearest = eq_id return nearest if min_dist < 50.0 else None # ์ž„๊ณ„๊ฐ’ 50.0 def _find_connected_nodes(self, line_id, equipment_ids): # ๋ผ์ธ์˜ ์‹œ์ž‘/๋์ ์ด ์–ด๋–ค ์„ค๋น„ BBox์— ํฌํ•จ๋˜๋Š”์ง€ ํ™•์ธ # (์‹ค์ œ ๊ตฌํ˜„ ์‹œ line์˜ coordinates ํ™œ์šฉ) return [eq for eq in equipment_ids if self.G.nodes[eq]['bbox'].intersects(self.G.nodes[line_id]['bbox'])] # ์‹คํ–‰ builder = PidTopologyBuilder(geometric_data) builder.build_graph() graph = builder.G ``` ### 3.2 ์œ„์ƒ ๋ถ„์„ ์œ ํ‹ธ๋ฆฌํ‹ฐ: ์˜ํ–ฅ๋„ ๋ถ„์„ (Impact Analysis) ```python def analyze_impact(graph, start_node): """ํŠน์ • ์„ค๋น„ ์žฅ์•  ์‹œ ํ•˜๋ฅ˜(Downstream)์— ์˜ํ–ฅ์„ ๋ฐ›๋Š” ๋ชจ๋“  ๋…ธ๋“œ ์ถ”์ถœ""" # BFS๋ฅผ ํ†ตํ•ด ๋„๋‹ฌ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ๋…ธ๋“œ ํƒ์ƒ‰ impacted_nodes = nx.descendants(graph, start_node) return list(impacted_nodes) # ์˜ˆ: P-101 ํŽŒํ”„ ๊ณ ์žฅ ์‹œ ์˜ํ–ฅ ๋ถ„์„ affected = analyze_impact(graph, "node_P101") print(f"Impacted Equipment: {affected}") ``` --- ## ๐Ÿš€ 4. Phase 2 ์™„๋ฃŒ ๊ธฐ์ค€ (Definition of Done) - [ ] ๋ชจ๋“  ์„ค๋น„์™€ ๊ณ„๊ธฐ๊ฐ€ ๊ทธ๋ž˜ํ”„์˜ **๋…ธ๋“œ(Node)**๋กœ ๋ณ€ํ™˜๋˜์—ˆ๋Š”๊ฐ€? - [ ] ํƒœ๊ทธ์™€ ์„ค๋น„ ๊ฐ„์˜ **๋…ผ๋ฆฌ์  ์—ฐ๊ฒฐ(Association)**์ด ์ •ํ™•ํ•˜๊ฒŒ ๋งคํ•‘๋˜์—ˆ๋Š”๊ฐ€? - [ ] ๋ฐฐ๊ด€(Line)์„ ํ†ตํ•ด ์„ค๋น„ ๊ฐ„์˜ **๋ฌผ๋ฆฌ์  ์—ฐ๊ฒฐ(Pipe Edge)**์ด ์ƒ์„ฑ๋˜์—ˆ๋Š”๊ฐ€? - [ ] `nx.descendants` ๋“ฑ์„ ํ†ตํ•ด ํŠน์ • ๋…ธ๋“œ๋กœ๋ถ€ํ„ฐ์˜ **ํ๋ฆ„ ์ถ”์ (Flow Tracing)**์ด ๊ฐ€๋Šฅํ•œ๊ฐ€? - [ ] ์ƒ์„ฑ๋œ ๊ทธ๋ž˜ํ”„ ๊ตฌ์กฐ๊ฐ€ JSON(GraphML ๋“ฑ) ํ˜•ํƒœ๋กœ ์ €์žฅ๋˜์–ด Phase 3๋กœ ์ „๋‹ฌ ๊ฐ€๋Šฅํ•œ๊ฐ€?