Merge pull request 'new-GNN_ENCODER-LayoutGMN' (#1) from new-GNN_ENCODER-LayoutGMN into main
Reviewed-on: http://192.168.31.243:3012/Jiao77/Geo-Layout-Transformer/pulls/1
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
reference/
|
||||||
|
.venv/
|
||||||
1
.python-version
Normal file
1
.python-version
Normal file
@@ -0,0 +1 @@
|
|||||||
|
3.12
|
||||||
48
README.md
48
README.md
@@ -1,3 +1,24 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/images/logo.png" width="240px" alt="Geo-Layout Transformer"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/your-username/Geo-Layout-Transformer/stargazers"><img src="https://img.shields.io/github/stars/your-username/Geo-Layout-Transformer.svg" /></a>
|
||||||
|
<a href="https://github.com/your-username/Geo-Layout-Transformer/network/members"><img src="https://img.shields.io/github/forks/your-username/Geo-Layout-Transformer.svg" /></a>
|
||||||
|
<a href="https://github.com/your-username/Geo-Layout-Transformer/issues"><img src="https://img.shields.io/github/issues-raw/your-username/Geo-Layout-Transformer" /></a>
|
||||||
|
<a href="https://github.com/your-username/Geo-Layout-Transformer/issues?q=is%3Aissue+is%3Aclosed"><img src="https://img.shields.io/github/issues-closed-raw/your-username/Geo-Layout-Transformer" /></a>
|
||||||
|
<a><img src="https://img.shields.io/badge/python-3.9%2B-blue" /></a>
|
||||||
|
<a><img src="https://img.shields.io/badge/PyTorch-2.x-orange" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="README.md">English</a> | <a href="README_zh.md">简体中文</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
# Geo-Layout Transformer 🚀
|
# Geo-Layout Transformer 🚀
|
||||||
|
|
||||||
**A Unified, Self-Supervised Foundation Model for Physical Design Analysis**
|
**A Unified, Self-Supervised Foundation Model for Physical Design Analysis**
|
||||||
@@ -124,6 +145,22 @@ The first step is to convert your GDSII/OASIS files into a graph dataset that th
|
|||||||
```
|
```
|
||||||
This script will parse the GDS file, divide it into patches, construct a graph for each patch, and save the processed data as `.pt` files for efficient loading.
|
This script will parse the GDS file, divide it into patches, construct a graph for each patch, and save the processed data as `.pt` files for efficient loading.
|
||||||
|
|
||||||
|
#### Polygon handling and per-patch graphs 🧩
|
||||||
|
|
||||||
|
When building a graph for each patch, we now preserve both global and per-patch (clipped) polygon information to robustly handle polygons spanning multiple patches:
|
||||||
|
|
||||||
|
- Each geometry retains:
|
||||||
|
- **Global polygon**: vertices, bbox, area.
|
||||||
|
- **Clipped polygon(s)** in the patch: vertices (may be multiple fragments), area, and the **area ratio** (clipped/global).
|
||||||
|
- **is_partial** flag indicating cross-patch polygons.
|
||||||
|
- **Layer index** and the **patch bbox**.
|
||||||
|
- Node features include centroid, width/height from clipped shape (or global if no clip), clipped area, area ratio, layer id, and partial flag.
|
||||||
|
- Extra metadata is attached on the PyG `Data` object:
|
||||||
|
- `data.layer: LongTensor [num_nodes]`
|
||||||
|
- `data.node_meta: List[Dict]` with per-node global/clipped details (for visualization/debugging)
|
||||||
|
|
||||||
|
This follows the spirit of LayoutGMN’s structural encoding while staying compatible with our GNN encoder.
|
||||||
|
|
||||||
### 4.2. Stage 2: Model Training
|
### 4.2. Stage 2: Model Training
|
||||||
|
|
||||||
Once the dataset is ready, you can train the Geo-Layout Transformer.
|
Once the dataset is ready, you can train the Geo-Layout Transformer.
|
||||||
@@ -159,6 +196,17 @@ This project is ambitious and we welcome contributions. Our future roadmap inclu
|
|||||||
|
|
||||||
Please feel free to open an issue or submit a pull request.
|
Please feel free to open an issue or submit a pull request.
|
||||||
|
|
||||||
|
## Acknowledgments
|
||||||
|
|
||||||
|
We stand on the shoulders of open-source communities. This project draws inspiration and/or utilities from:
|
||||||
|
|
||||||
|
- PyTorch and PyTorch Geometric for model building and graph learning
|
||||||
|
- gdstk/klayout for GDSII/OASIS parsing and geometry operations
|
||||||
|
- Scientific Python stack (NumPy, SciPy) for numerical robustness
|
||||||
|
- Research works such as LayoutGMN (graph matching for structural similarity) that informed our polygon/graph handling design
|
||||||
|
|
||||||
|
If your work is used and not listed here, please open an issue or PR so we can properly credit you.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Made with ❤️ for EDA research and open-source collaboration.
|
Made with ❤️ for EDA research and open-source collaboration.
|
||||||
|
|||||||
48
README_zh.md
48
README_zh.md
@@ -1,3 +1,24 @@
|
|||||||
|
<div align="center">
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
<img src="docs/images/logo.png" width="240px" alt="Geo-Layout Transformer"/>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/your-username/Geo-Layout-Transformer/stargazers"><img src="https://img.shields.io/github/stars/your-username/Geo-Layout-Transformer.svg" /></a>
|
||||||
|
<a href="https://github.com/your-username/Geo-Layout-Transformer/network/members"><img src="https://img.shields.io/github/forks/your-username/Geo-Layout-Transformer.svg" /></a>
|
||||||
|
<a href="https://github.com/your-username/Geo-Layout-Transformer/issues"><img src="https://img.shields.io/github/issues-raw/your-username/Geo-Layout-Transformer" /></a>
|
||||||
|
<a href="https://github.com/your-username/Geo-Layout-Transformer/issues?q=is%3Aissue+is%3Aclosed"><img src="https://img.shields.io/github/issues-closed-raw/your-username/Geo-Layout-Transformer" /></a>
|
||||||
|
<a><img src="https://img.shields.io/badge/python-3.9%2B-blue" /></a>
|
||||||
|
<a><img src="https://img.shields.io/badge/PyTorch-2.x-orange" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<a href="README.md">English</a> | <a href="README_zh.md">简体中文</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
# Geo-Layout Transformer 🚀
|
# Geo-Layout Transformer 🚀
|
||||||
|
|
||||||
**一个用于物理设计分析的统一、自监督基础模型**
|
**一个用于物理设计分析的统一、自监督基础模型**
|
||||||
@@ -124,6 +145,22 @@ Geo-Layout-Transformer/
|
|||||||
```
|
```
|
||||||
该脚本将解析 GDS 文件,将其划分为多个区块,为每个区块构建一个图,并将处理后的数据保存为 `.pt` 文件以便高效加载。
|
该脚本将解析 GDS 文件,将其划分为多个区块,为每个区块构建一个图,并将处理后的数据保存为 `.pt` 文件以便高效加载。
|
||||||
|
|
||||||
|
#### 多边形处理与按区块建图 🧩
|
||||||
|
|
||||||
|
在为每个区块(patch)构建图时,我们同时保留多边形的全局信息和区块内(裁剪后)的信息,以稳健处理跨越多个区块的多边形:
|
||||||
|
|
||||||
|
- 每个几何对象包含:
|
||||||
|
- **全局多边形**:顶点、外接框、面积。
|
||||||
|
- **区块内裁剪多边形(可能多个片段)**:顶点、面积,以及 **面积占比**(裁剪/全局)。
|
||||||
|
- **is_partial 标记**:指示是否跨区块。
|
||||||
|
- **层索引** 与 **区块边界框**。
|
||||||
|
- 节点特征包含:基于裁剪形状(若无则基于全局)的质心、宽/高、裁剪面积、面积占比、层 id、是否跨区块标志。
|
||||||
|
- 额外元数据保存在 PyG `Data` 对象中:
|
||||||
|
- `data.layer: LongTensor [num_nodes]`
|
||||||
|
- `data.node_meta: List[Dict]`,含每个节点的全局/裁剪细节(用于可视化/调试)
|
||||||
|
|
||||||
|
该设计借鉴了 LayoutGMN 的结构编码思想,同时与我们现有的 GNN 编码器保持兼容。
|
||||||
|
|
||||||
### 4.2. 阶段二:模型训练
|
### 4.2. 阶段二:模型训练
|
||||||
|
|
||||||
数据集准备就绪后,您就可以开始训练 Geo-Layout Transformer。
|
数据集准备就绪后,您就可以开始训练 Geo-Layout Transformer。
|
||||||
@@ -159,6 +196,17 @@ python main.py --config-file configs/default.yaml --mode pretrain --data-dir dat
|
|||||||
|
|
||||||
欢迎随时提出 Issue 或提交 Pull Request。
|
欢迎随时提出 Issue 或提交 Pull Request。
|
||||||
|
|
||||||
|
## 致谢
|
||||||
|
|
||||||
|
本项目离不开开源社区的贡献与启发,特别感谢:
|
||||||
|
|
||||||
|
- PyTorch 与 PyTorch Geometric,为模型构建与图学习提供可靠基石
|
||||||
|
- gdstk/klayout,为 GDSII/OASIS 的解析与几何操作提供高效能力
|
||||||
|
- 科学计算生态(NumPy、SciPy),保障数值计算的稳定性
|
||||||
|
- 研究工作 LayoutGMN(面向结构相似性的图匹配),启发了我们对多边形/图构建的设计
|
||||||
|
|
||||||
|
若您的工作被本项目使用但尚未列出,欢迎提交 Issue 或 PR 以便完善致谢。
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
Made with ❤️ 面向 EDA 研究与开源协作。
|
Made with ❤️ 面向 EDA 研究与开源协作。
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
[project]
|
[project]
|
||||||
name = "layouttrans"
|
name = "geo-layout-transformer"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
description = "Add your description here"
|
description = "Add your description here"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
requires-python = ">=3.13"
|
requires-python = ">=3.12"
|
||||||
dependencies = []
|
dependencies = []
|
||||||
|
|||||||
BIN
reference/.DS_Store
vendored
Normal file
BIN
reference/.DS_Store
vendored
Normal file
Binary file not shown.
@@ -39,30 +39,76 @@ class GDSParser:
|
|||||||
return patches
|
return patches
|
||||||
|
|
||||||
def extract_geometries_from_patch(self, patch_bbox: Tuple[float, float, float, float]) -> List[Dict]:
|
def extract_geometries_from_patch(self, patch_bbox: Tuple[float, float, float, float]) -> List[Dict]:
|
||||||
"""从给定的区块中提取所有几何对象。
|
"""从给定的区块中提取所有几何对象,并记录全局与区块内(裁剪后)的信息。
|
||||||
|
|
||||||
|
说明:
|
||||||
|
- 为了处理跨越多个区块的多边形,本函数会计算多边形与区块边界框的布尔相交,
|
||||||
|
得到位于该区块内的裁剪多边形,并同时记录原始(全局)多边形信息。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
patch_bbox: 区块的边界框 (x_min, y_min, x_max, y_max)。
|
patch_bbox: 区块的边界框 (x_min, y_min, x_max, y_max)。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
一个字典列表,每个字典代表一个几何对象及其属性(多边形、层、边界框)。
|
一个字典列表,每个字典代表一个几何对象及其属性:
|
||||||
|
- global_points: 原始多边形顶点(Nx2 ndarray)
|
||||||
|
- global_bbox: 原始多边形边界框
|
||||||
|
- global_area: 原始多边形面积
|
||||||
|
- clipped_points: 与区块相交后的裁剪多边形顶点(Mx2 ndarray,可能为空)
|
||||||
|
- clipped_area: 裁剪后面积(可能为 0)
|
||||||
|
- area_ratio: 裁剪面积 / 原始面积(用于衡量跨区块比例)
|
||||||
|
- is_partial: 是否为跨区块(裁剪面积 < 原始面积)
|
||||||
|
- layer: 层映射到的整数索引
|
||||||
|
- patch_bbox: 当前区块边界框
|
||||||
"""
|
"""
|
||||||
x_min, y_min, x_max, y_max = patch_bbox
|
x_min, y_min, x_max, y_max = patch_bbox
|
||||||
# 获取单元内的所有多边形
|
rect = gdstk.rectangle(x_min, y_min, x_max, y_max)
|
||||||
polygons = self.top_cell.get_polygons(by_spec=True)
|
polygons = self.top_cell.get_polygons(by_spec=True)
|
||||||
geometries = []
|
geometries: List[Dict] = []
|
||||||
# 遍历所有多边形
|
|
||||||
for (layer, datatype), poly_list in polygons.items():
|
for (layer, datatype), poly_list in polygons.items():
|
||||||
layer_str = f"{layer}/{datatype}"
|
layer_str = f"{layer}/{datatype}"
|
||||||
# 只处理在 layer_mapping 中定义的层
|
if layer_str not in self.layer_mapping:
|
||||||
if layer_str in self.layer_mapping:
|
continue
|
||||||
for poly in poly_list:
|
layer_idx = self.layer_mapping[layer_str]
|
||||||
# 简单的边界框相交检查
|
|
||||||
p_xmin, p_ymin, p_xmax, p_ymax = poly.bb()
|
for poly in poly_list:
|
||||||
if not (p_xmax < x_min or p_xmin > x_max or p_ymax < y_min or p_ymin > y_max):
|
p_xmin, p_ymin, p_xmax, p_ymax = poly.bb()
|
||||||
geometries.append({
|
# 快速边界框测试(若无相交则跳过)
|
||||||
"polygon": poly,
|
if p_xmax < x_min or p_xmin > x_max or p_ymax < y_min or p_ymin > y_max:
|
||||||
"layer": self.layer_mapping[layer_str],
|
continue
|
||||||
"bbox": (p_xmin, p_ymin, p_xmax, p_ymax)
|
|
||||||
})
|
# 全局多边形点与面积
|
||||||
|
global_points = np.array(poly.points, dtype=float)
|
||||||
|
global_area = abs(gdstk.Polygon(global_points).area())
|
||||||
|
|
||||||
|
# 与区块矩形做相交,可能返回多个多边形
|
||||||
|
clipped = gdstk.boolean([poly], [rect], "and", precision=1e-3, layer=layer, datatype=datatype)
|
||||||
|
clipped_points_list: List[np.ndarray] = []
|
||||||
|
clipped_area = 0.0
|
||||||
|
if clipped:
|
||||||
|
for cpoly in clipped:
|
||||||
|
pts = np.array(cpoly.points, dtype=float)
|
||||||
|
if pts.size == 0:
|
||||||
|
continue
|
||||||
|
area = abs(gdstk.Polygon(pts).area())
|
||||||
|
if area <= 0:
|
||||||
|
continue
|
||||||
|
clipped_points_list.append(pts)
|
||||||
|
clipped_area += area
|
||||||
|
|
||||||
|
area_ratio = (clipped_area / global_area) if global_area > 0 else 0.0
|
||||||
|
is_partial = area_ratio < 0.999 # 允许微小数值误差
|
||||||
|
|
||||||
|
geometries.append({
|
||||||
|
"global_points": global_points,
|
||||||
|
"global_bbox": (p_xmin, p_ymin, p_xmax, p_ymax),
|
||||||
|
"global_area": float(global_area),
|
||||||
|
"clipped_points_list": clipped_points_list, # 可能包含多个裁剪片段
|
||||||
|
"clipped_area": float(clipped_area),
|
||||||
|
"area_ratio": float(area_ratio),
|
||||||
|
"is_partial": bool(is_partial),
|
||||||
|
"layer": layer_idx,
|
||||||
|
"patch_bbox": (x_min, y_min, x_max, y_max),
|
||||||
|
})
|
||||||
|
|
||||||
return geometries
|
return geometries
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from typing import List, Dict
|
from typing import List, Dict, Tuple
|
||||||
import torch
|
import torch
|
||||||
from torch_geometric.data import Data
|
from torch_geometric.data import Data
|
||||||
from scipy.spatial import cKDTree
|
from scipy.spatial import cKDTree
|
||||||
@@ -32,21 +32,64 @@ class GraphConstructor:
|
|||||||
if not geometries:
|
if not geometries:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
node_features = []
|
node_features: List[List[float]] = []
|
||||||
node_positions = []
|
node_positions: List[List[float]] = []
|
||||||
# 提取每个几何图形的特征
|
node_layers: List[int] = []
|
||||||
for geo in geometries:
|
node_meta: List[Dict] = []
|
||||||
x_min, y_min, x_max, y_max = geo["bbox"]
|
|
||||||
width = x_max - x_min
|
# 提取每个几何图形的特征(优先使用裁剪后片段的质心;若无裁剪片段,则使用全局质心)
|
||||||
height = y_max - y_min
|
for geo in geometries:
|
||||||
area = width * height
|
layer_idx: int = int(geo["layer"]) if "layer" in geo else 0
|
||||||
centroid_x = x_min + width / 2
|
global_bbox = geo.get("global_bbox", None)
|
||||||
centroid_y = y_min + height / 2
|
global_points = geo.get("global_points", None)
|
||||||
|
clipped_points_list = geo.get("clipped_points_list", []) or []
|
||||||
|
clipped_area = float(geo.get("clipped_area", 0.0))
|
||||||
|
global_area = float(geo.get("global_area", 0.0))
|
||||||
|
area_ratio = float(geo.get("area_ratio", 0.0))
|
||||||
|
is_partial = bool(geo.get("is_partial", False))
|
||||||
|
|
||||||
|
# 选择用于节点位置与宽高的几何:若存在裁剪片段,聚合其外接框,否则用全局框
|
||||||
|
if clipped_points_list:
|
||||||
|
# 合并所有裁剪片段点,计算整体外接框与质心
|
||||||
|
all_pts = np.vstack(clipped_points_list)
|
||||||
|
elif global_points is not None:
|
||||||
|
all_pts = np.array(global_points, dtype=float)
|
||||||
|
else:
|
||||||
|
# 回退到 bbox 信息(兼容旧格式)
|
||||||
|
x_min, y_min, x_max, y_max = geo["bbox"]
|
||||||
|
all_pts = np.array([[x_min, y_min], [x_max, y_max]], dtype=float)
|
||||||
|
|
||||||
|
x_min, y_min = np.min(all_pts, axis=0)
|
||||||
|
x_max, y_max = np.max(all_pts, axis=0)
|
||||||
|
width = float(x_max - x_min)
|
||||||
|
height = float(y_max - y_min)
|
||||||
|
centroid_x = float(x_min + width / 2.0)
|
||||||
|
centroid_y = float(y_min + height / 2.0)
|
||||||
|
|
||||||
|
# 节点特征:质心、宽、高、裁剪面积、全局面积占比、层索引(数值化)
|
||||||
|
features = [
|
||||||
|
centroid_x,
|
||||||
|
centroid_y,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
clipped_area,
|
||||||
|
(clipped_area / global_area) if global_area > 0 else 0.0,
|
||||||
|
float(layer_idx),
|
||||||
|
1.0 if is_partial else 0.0,
|
||||||
|
]
|
||||||
|
|
||||||
# 特征包括:中心点坐标、宽度、高度、面积
|
|
||||||
features = [centroid_x, centroid_y, width, height, area]
|
|
||||||
node_features.append(features)
|
node_features.append(features)
|
||||||
node_positions.append([centroid_x, centroid_y])
|
node_positions.append([centroid_x, centroid_y])
|
||||||
|
node_layers.append(layer_idx)
|
||||||
|
# 将原始与裁剪的必要元信息保存在 Data 中(以便后续可视化与调试)
|
||||||
|
node_meta.append({
|
||||||
|
"layer": layer_idx,
|
||||||
|
"global_bbox": tuple(global_bbox) if global_bbox is not None else None,
|
||||||
|
"global_area": global_area,
|
||||||
|
"clipped_area": clipped_area,
|
||||||
|
"area_ratio": area_ratio,
|
||||||
|
"is_partial": is_partial,
|
||||||
|
})
|
||||||
|
|
||||||
# 将特征和位置转换为 PyTorch 张量
|
# 将特征和位置转换为 PyTorch 张量
|
||||||
x = torch.tensor(node_features, dtype=torch.float)
|
x = torch.tensor(node_features, dtype=torch.float)
|
||||||
@@ -57,6 +100,9 @@ class GraphConstructor:
|
|||||||
|
|
||||||
# 创建图数据对象
|
# 创建图数据对象
|
||||||
data = Data(x=x, edge_index=edge_index, pos=pos, y=torch.tensor([label], dtype=torch.float))
|
data = Data(x=x, edge_index=edge_index, pos=pos, y=torch.tensor([label], dtype=torch.float))
|
||||||
|
# 附加层索引与元信息(元信息以对象列表形式保存,供上层使用;不会参与张量运算)
|
||||||
|
data.layer = torch.tensor(node_layers, dtype=torch.long)
|
||||||
|
data.node_meta = node_meta
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def _create_edges(self, node_positions: torch.Tensor) -> torch.Tensor:
|
def _create_edges(self, node_positions: torch.Tensor) -> torch.Tensor:
|
||||||
|
|||||||
Reference in New Issue
Block a user