Skip to content

Merge

merge

MergeDiff dataclass

Dataclass to hold geometric info about the layout diff.

Source code in kfactory/merge.py
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
@dataclass
class MergeDiff:
    """Dataclass to hold geometric info about the layout diff."""

    model_config = ConfigDict(arbitrary_types_allowed=True)

    layout_a: kdb.Layout
    layout_b: kdb.Layout
    name_a: str
    name_b: str
    dbu_differs: bool = False
    cell_a: kdb.Cell = field(init=False)
    cell_b: kdb.Cell = field(init=False)
    layer: kdb.LayerInfo = field(init=False)
    layer_a: int = field(init=False)
    layer_b: int = field(init=False)
    diff_xor: kdb.Layout = field(init=False)
    diff_a: kdb.Layout = field(init=False)
    diff_b: kdb.Layout = field(init=False)
    layout_meta_diff: dict[str, MetaData] = field(init=False)
    cells_meta_diff: dict[str, dict[str, MetaData]] = field(init=False)
    kdiff: kdb.LayoutDiff = field(init=False)
    loglevel: LogLevel | int = field(default=LogLevel.CRITICAL)
    """Log level at which to log polygon errors."""

    def __post_init__(self) -> None:
        """Initialize the DiffInfo."""
        self.diff_xor = kdb.Layout()
        self.layout_meta_diff = {}
        self.cells_meta_diff = defaultdict(dict)
        self.diff_a = kdb.Layout()
        self.diff_b = kdb.Layout()
        self.kdiff = kdb.LayoutDiff()
        self.kdiff.on_begin_cell = self.on_begin_cell  # ty:ignore[invalid-assignment]
        self.kdiff.on_begin_layer = self.on_begin_layer  # ty:ignore[invalid-assignment]
        self.kdiff.on_end_layer = self.on_end_layer  # ty:ignore[invalid-assignment]
        self.kdiff.on_instance_in_a_only = self.on_instance_in_a_only  # ty:ignore[invalid-assignment]
        self.kdiff.on_instance_in_b_only = self.on_instance_in_b_only  # ty:ignore[invalid-assignment]
        self.kdiff.on_polygon_in_a_only = self.on_polygon_in_a_only  # ty:ignore[invalid-assignment]
        self.kdiff.on_polygon_in_b_only = self.on_polygon_in_b_only  # ty:ignore[invalid-assignment]
        self.kdiff.on_cell_meta_info_differs = self.on_cell_meta_info_differs  # ty:ignore[invalid-assignment]

    def on_dbu_differs(self, dbu_a: float, dbu_b: float) -> None:
        """Called when the DBU differs between the two layouts."""
        if self.loglevel is not None:
            logger.log(
                self.loglevel,
                f"DBU differs between existing layout '{dbu_a!s}'"
                f" and the new layout '{dbu_b!s}'.",
            )
        self.dbu_differs = True

    def on_begin_cell(self, cell_a: kdb.Cell, cell_b: kdb.Cell) -> None:
        """Set the cells to the new cell."""
        self.cell_a = self.diff_a.create_cell(cell_a.name)
        self.cell_b = self.diff_b.create_cell(cell_b.name)

    def on_begin_layer(self, layer: kdb.LayerInfo, layer_a: int, layer_b: int) -> None:
        """Set the layers to the new layer."""
        self.layer = layer
        self.layer_a = self.diff_a.layer(layer)
        self.layer_b = self.diff_b.layer(layer)

    def on_polygon_in_a_only(self, poly: kdb.Polygon, propid: int) -> None:
        """Called when there is only a polygon in the cell_a."""
        if self.loglevel is not None:
            logger.log(self.loglevel, f"Found {poly=} in {self.name_a} only.")
        self.cell_a.shapes(self.layer_a).insert(poly)

    def on_instance_in_a_only(self, instance: kdb.CellInstArray, propid: int) -> None:
        """Called when there is only an instance in the cell_a."""
        if self.loglevel is not None:
            logger.log(self.loglevel, f"Found {instance=} in {self.name_a} only.")
        cell = self.layout_a.cell(instance.cell_index)

        regions: list[kdb.Region] = []
        layers = list(cell.layout().layer_indexes())
        layer_infos = list(cell.layout().layer_infos())

        for layer in layers:
            r = kdb.Region()
            r.insert(self.layout_a.cell(instance.cell_index).begin_shapes_rec(layer))
            regions.append(r)

        for trans in instance.each_cplx_trans():
            for li, r in zip(layer_infos, regions, strict=False):
                self.cell_a.shapes(self.diff_a.layer(li)).insert(r.transformed(trans))

    def on_instance_in_b_only(self, instance: kdb.CellInstArray, propid: int) -> None:
        """Called when there is only an instance in the cell_b."""
        if self.loglevel is not None:
            logger.log(self.loglevel, f"Found {instance=} in {self.name_b} only.")
        cell = self.layout_b.cell(instance.cell_index)

        regions: list[kdb.Region] = []
        layers = list(cell.layout().layer_indexes())
        layer_infos = list(cell.layout().layer_infos())

        for layer in layers:
            r = kdb.Region()
            r.insert(self.layout_b.cell(instance.cell_index).begin_shapes_rec(layer))
            regions.append(r)

        for trans in instance.each_cplx_trans():
            for li, r in zip(layer_infos, regions, strict=False):
                self.cell_b.shapes(self.diff_b.layer(li)).insert(r.transformed(trans))

    def on_polygon_in_b_only(self, poly: kdb.Polygon, propid: int) -> None:
        """Called when there is only a polygon in the cell_b."""
        if self.loglevel is not None:
            logger.log(self.loglevel, f"Found {poly=} in {self.name_b} only.")
        self.cell_b.shapes(self.layer_b).insert(poly)

    def on_end_layer(self) -> None:
        """Before switching to a new layer, copy the xor to the xor layout."""
        if (not self.cell_a.bbox().empty()) or (not self.cell_b.bbox().empty()):
            c: kdb.Cell = self.diff_xor.cell(self.cell_a.name)
            if c is None:
                c = self.diff_xor.create_cell(self.cell_a.name)

            c.shapes(self.diff_xor.layer(self.layer)).insert(
                kdb.Region(self.cell_a.shapes(self.layer_a))
                ^ kdb.Region(self.cell_b.shapes(self.layer_b))
            )

    def on_cell_meta_info_differs(
        self,
        name: str,
        meta_a: kdb.LayoutMetaInfo | None,
        meta_b: kdb.LayoutMetaInfo | None,
    ) -> None:
        """Called when there is a difference in meta infos of cells."""
        if meta_a is None:
            logger.log(
                self.loglevel,
                f"Found '{name}' MetaInfo in loaded Layout's cell '{self.cell_b.name}'"
                f" with value '{meta_b!s}' but it's not in the existing Layout.",
            )
        elif meta_b is None:
            logger.log(
                self.loglevel,
                f"Found '{name}' MetaInfo in existing Layout's cell '{self.cell_a.name}"
                f"' with value '{meta_a!s}' but it's not in the existing Layout.",
            )
        else:
            logger.error(
                f"'{name}' MetaInfo differs between existing '{meta_a!s}' and"
                f" loaded '{meta_b!s}'"
            )
            self.cells_meta_diff[self.cell_a.name][name] = {
                "existing": str(meta_a),
                "loaded": str(meta_b),
            }
        dc = self.diff_xor.cell(self.cell_a.name) or self.diff_xor.create_cell(
            self.cell_a.name
        )
        dc.add_meta_info(
            kdb.LayoutMetaInfo(name, {"a": meta_a, "b": meta_b}, persisted=True)
        )

    def compare(self) -> bool:
        """Run the comparing.

        Returns: True if there are differences, nothing otherwise
        """
        return self.kdiff.compare(
            self.layout_a,
            self.layout_b,
            kdb.LayoutDiff.Verbose
            | kdb.LayoutDiff.NoLayerNames
            | kdb.LayoutDiff.BoxesAsPolygons
            | kdb.LayoutDiff.PathsAsPolygons
            | kdb.LayoutDiff.IgnoreDuplicates
            | kdb.LayoutDiff.WithMetaInfo,
        )

loglevel class-attribute instance-attribute

loglevel: LogLevel | int = field(default=CRITICAL)

Log level at which to log polygon errors.

__post_init__

__post_init__() -> None

Initialize the DiffInfo.

Source code in kfactory/merge.py
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
def __post_init__(self) -> None:
    """Initialize the DiffInfo."""
    self.diff_xor = kdb.Layout()
    self.layout_meta_diff = {}
    self.cells_meta_diff = defaultdict(dict)
    self.diff_a = kdb.Layout()
    self.diff_b = kdb.Layout()
    self.kdiff = kdb.LayoutDiff()
    self.kdiff.on_begin_cell = self.on_begin_cell  # ty:ignore[invalid-assignment]
    self.kdiff.on_begin_layer = self.on_begin_layer  # ty:ignore[invalid-assignment]
    self.kdiff.on_end_layer = self.on_end_layer  # ty:ignore[invalid-assignment]
    self.kdiff.on_instance_in_a_only = self.on_instance_in_a_only  # ty:ignore[invalid-assignment]
    self.kdiff.on_instance_in_b_only = self.on_instance_in_b_only  # ty:ignore[invalid-assignment]
    self.kdiff.on_polygon_in_a_only = self.on_polygon_in_a_only  # ty:ignore[invalid-assignment]
    self.kdiff.on_polygon_in_b_only = self.on_polygon_in_b_only  # ty:ignore[invalid-assignment]
    self.kdiff.on_cell_meta_info_differs = self.on_cell_meta_info_differs  # ty:ignore[invalid-assignment]

compare

compare() -> bool

Run the comparing.

Returns: True if there are differences, nothing otherwise

Source code in kfactory/merge.py
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
def compare(self) -> bool:
    """Run the comparing.

    Returns: True if there are differences, nothing otherwise
    """
    return self.kdiff.compare(
        self.layout_a,
        self.layout_b,
        kdb.LayoutDiff.Verbose
        | kdb.LayoutDiff.NoLayerNames
        | kdb.LayoutDiff.BoxesAsPolygons
        | kdb.LayoutDiff.PathsAsPolygons
        | kdb.LayoutDiff.IgnoreDuplicates
        | kdb.LayoutDiff.WithMetaInfo,
    )

on_begin_cell

on_begin_cell(cell_a: Cell, cell_b: Cell) -> None

Set the cells to the new cell.

Source code in kfactory/merge.py
70
71
72
73
def on_begin_cell(self, cell_a: kdb.Cell, cell_b: kdb.Cell) -> None:
    """Set the cells to the new cell."""
    self.cell_a = self.diff_a.create_cell(cell_a.name)
    self.cell_b = self.diff_b.create_cell(cell_b.name)

on_begin_layer

on_begin_layer(
    layer: LayerInfo, layer_a: int, layer_b: int
) -> None

Set the layers to the new layer.

Source code in kfactory/merge.py
75
76
77
78
79
def on_begin_layer(self, layer: kdb.LayerInfo, layer_a: int, layer_b: int) -> None:
    """Set the layers to the new layer."""
    self.layer = layer
    self.layer_a = self.diff_a.layer(layer)
    self.layer_b = self.diff_b.layer(layer)

on_cell_meta_info_differs

on_cell_meta_info_differs(
    name: str,
    meta_a: LayoutMetaInfo | None,
    meta_b: LayoutMetaInfo | None,
) -> None

Called when there is a difference in meta infos of cells.

Source code in kfactory/merge.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
def on_cell_meta_info_differs(
    self,
    name: str,
    meta_a: kdb.LayoutMetaInfo | None,
    meta_b: kdb.LayoutMetaInfo | None,
) -> None:
    """Called when there is a difference in meta infos of cells."""
    if meta_a is None:
        logger.log(
            self.loglevel,
            f"Found '{name}' MetaInfo in loaded Layout's cell '{self.cell_b.name}'"
            f" with value '{meta_b!s}' but it's not in the existing Layout.",
        )
    elif meta_b is None:
        logger.log(
            self.loglevel,
            f"Found '{name}' MetaInfo in existing Layout's cell '{self.cell_a.name}"
            f"' with value '{meta_a!s}' but it's not in the existing Layout.",
        )
    else:
        logger.error(
            f"'{name}' MetaInfo differs between existing '{meta_a!s}' and"
            f" loaded '{meta_b!s}'"
        )
        self.cells_meta_diff[self.cell_a.name][name] = {
            "existing": str(meta_a),
            "loaded": str(meta_b),
        }
    dc = self.diff_xor.cell(self.cell_a.name) or self.diff_xor.create_cell(
        self.cell_a.name
    )
    dc.add_meta_info(
        kdb.LayoutMetaInfo(name, {"a": meta_a, "b": meta_b}, persisted=True)
    )

on_dbu_differs

on_dbu_differs(dbu_a: float, dbu_b: float) -> None

Called when the DBU differs between the two layouts.

Source code in kfactory/merge.py
60
61
62
63
64
65
66
67
68
def on_dbu_differs(self, dbu_a: float, dbu_b: float) -> None:
    """Called when the DBU differs between the two layouts."""
    if self.loglevel is not None:
        logger.log(
            self.loglevel,
            f"DBU differs between existing layout '{dbu_a!s}'"
            f" and the new layout '{dbu_b!s}'.",
        )
    self.dbu_differs = True

on_end_layer

on_end_layer() -> None

Before switching to a new layer, copy the xor to the xor layout.

Source code in kfactory/merge.py
131
132
133
134
135
136
137
138
139
140
141
def on_end_layer(self) -> None:
    """Before switching to a new layer, copy the xor to the xor layout."""
    if (not self.cell_a.bbox().empty()) or (not self.cell_b.bbox().empty()):
        c: kdb.Cell = self.diff_xor.cell(self.cell_a.name)
        if c is None:
            c = self.diff_xor.create_cell(self.cell_a.name)

        c.shapes(self.diff_xor.layer(self.layer)).insert(
            kdb.Region(self.cell_a.shapes(self.layer_a))
            ^ kdb.Region(self.cell_b.shapes(self.layer_b))
        )

on_instance_in_a_only

on_instance_in_a_only(
    instance: CellInstArray, propid: int
) -> None

Called when there is only an instance in the cell_a.

Source code in kfactory/merge.py
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
def on_instance_in_a_only(self, instance: kdb.CellInstArray, propid: int) -> None:
    """Called when there is only an instance in the cell_a."""
    if self.loglevel is not None:
        logger.log(self.loglevel, f"Found {instance=} in {self.name_a} only.")
    cell = self.layout_a.cell(instance.cell_index)

    regions: list[kdb.Region] = []
    layers = list(cell.layout().layer_indexes())
    layer_infos = list(cell.layout().layer_infos())

    for layer in layers:
        r = kdb.Region()
        r.insert(self.layout_a.cell(instance.cell_index).begin_shapes_rec(layer))
        regions.append(r)

    for trans in instance.each_cplx_trans():
        for li, r in zip(layer_infos, regions, strict=False):
            self.cell_a.shapes(self.diff_a.layer(li)).insert(r.transformed(trans))

on_instance_in_b_only

on_instance_in_b_only(
    instance: CellInstArray, propid: int
) -> None

Called when there is only an instance in the cell_b.

Source code in kfactory/merge.py
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
def on_instance_in_b_only(self, instance: kdb.CellInstArray, propid: int) -> None:
    """Called when there is only an instance in the cell_b."""
    if self.loglevel is not None:
        logger.log(self.loglevel, f"Found {instance=} in {self.name_b} only.")
    cell = self.layout_b.cell(instance.cell_index)

    regions: list[kdb.Region] = []
    layers = list(cell.layout().layer_indexes())
    layer_infos = list(cell.layout().layer_infos())

    for layer in layers:
        r = kdb.Region()
        r.insert(self.layout_b.cell(instance.cell_index).begin_shapes_rec(layer))
        regions.append(r)

    for trans in instance.each_cplx_trans():
        for li, r in zip(layer_infos, regions, strict=False):
            self.cell_b.shapes(self.diff_b.layer(li)).insert(r.transformed(trans))

on_polygon_in_a_only

on_polygon_in_a_only(poly: Polygon, propid: int) -> None

Called when there is only a polygon in the cell_a.

Source code in kfactory/merge.py
81
82
83
84
85
def on_polygon_in_a_only(self, poly: kdb.Polygon, propid: int) -> None:
    """Called when there is only a polygon in the cell_a."""
    if self.loglevel is not None:
        logger.log(self.loglevel, f"Found {poly=} in {self.name_a} only.")
    self.cell_a.shapes(self.layer_a).insert(poly)

on_polygon_in_b_only

on_polygon_in_b_only(poly: Polygon, propid: int) -> None

Called when there is only a polygon in the cell_b.

Source code in kfactory/merge.py
125
126
127
128
129
def on_polygon_in_b_only(self, poly: kdb.Polygon, propid: int) -> None:
    """Called when there is only a polygon in the cell_b."""
    if self.loglevel is not None:
        logger.log(self.loglevel, f"Found {poly=} in {self.name_b} only.")
    self.cell_b.shapes(self.layer_b).insert(poly)