Skip to content

Commit

Permalink
implement cross sections fully
Browse files Browse the repository at this point in the history
  • Loading branch information
sebastian-goeldi committed Feb 6, 2025
1 parent 7aa4793 commit 7401e6b
Show file tree
Hide file tree
Showing 7 changed files with 202 additions and 82 deletions.
188 changes: 143 additions & 45 deletions src/kfactory/cross_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
Self,
TypedDict,
cast,
overload,
)

from pydantic import BaseModel, Field, PrivateAttr, model_validator
Expand All @@ -26,22 +27,22 @@
__all__ = ["CrossSection", "DCrossSection", "SymmetricalCrossSection"]


class SymmetricalCrossSection(BaseModel, frozen=True):
class SymmetricalCrossSection(BaseModel, frozen=True, arbitrary_types_allowed=True):
"""CrossSection which is symmetrical to its main_layer/width."""

width: int
enclosure: LayerEnclosure
name: str = ""
radius: int | None = None
radius_min: int | None = None
bbox_sections: dict[kdb.LayerInfo, tuple[int, int, int, int]]
bbox_sections: dict[kdb.LayerInfo, int]

def __init__(
self,
width: int,
enclosure: LayerEnclosure,
name: str | None = None,
bbox_sections: dict[kdb.LayerInfo, tuple[int, int, int, int]] | None = None,
bbox_sections: dict[kdb.LayerInfo, int] | None = None,
) -> None:
"""Initialized the CrossSection."""
super().__init__(
Expand Down Expand Up @@ -96,20 +97,36 @@ def get_xmax(self) -> int:
)


class TCrossSection(BaseModel, ABC, Generic[TUnit], frozen=True):
class TCrossSection(ABC, Generic[TUnit]):
_base: SymmetricalCrossSection = PrivateAttr()
kcl: KCLayout
name: str

@overload
def __init__(self, kcl: KCLayout, *, base: SymmetricalCrossSection) -> None: ...

@overload
def __init__(
self,
kcl: KCLayout,
width: TUnit,
layer: kdb.LayerInfo,
sections: list[tuple[TUnit, TUnit] | tuple[TUnit]],
sections: Sequence[tuple[TUnit, TUnit] | tuple[TUnit]],
radius: TUnit | None = None,
radius_min: TUnit | None = None,
bbox_layers: Sequence[kdb.LayerInfo] | None = None,
bbox_offsets: Sequence[TUnit] | None = None,
) -> None: ...

def __init__(
self,
kcl: KCLayout,
width: TUnit | None = None,
layer: kdb.LayerInfo | None = None,
sections: Sequence[tuple[TUnit, TUnit] | tuple[TUnit]] | None = None,
radius: TUnit | None = None,
radius_min: TUnit | None = None,
bbox_sections: Sequence[tuple[kdb.LayerInfo, TUnit, TUnit, TUnit, TUnit]] = [],
bbox_layers: Sequence[kdb.LayerInfo] | None = None,
bbox_offsets: Sequence[TUnit] | None = None,
base: SymmetricalCrossSection | None = None,
) -> None: ...

Expand Down Expand Up @@ -141,42 +158,74 @@ def radius_min(self) -> TUnit | None: ...
@abstractmethod
def bbox_sections(
self,
) -> dict[kdb.LayerInfo, tuple[TUnit, TUnit, TUnit, TUnit]]: ...
) -> dict[kdb.LayerInfo, TUnit]: ...

@abstractmethod
def get_xmin_xmax(self) -> tuple[TUnit, TUnit]: ...


class CrossSection(TCrossSection[int]):
@overload
def __init__(self, kcl: KCLayout, *, base: SymmetricalCrossSection) -> None: ...

@overload
def __init__(
self,
kcl: KCLayout,
width: int,
layer: kdb.LayerInfo,
sections: list[tuple[kdb.LayerInfo, int, int] | tuple[kdb.LayerInfo, int]],
sections: Sequence[tuple[kdb.LayerInfo, int, int] | tuple[kdb.LayerInfo, int]],
radius: int | None = None,
radius_min: int | None = None,
name: str | None = None,
bbox_layers: Sequence[kdb.LayerInfo] | None = None,
bbox_offsets: Sequence[int] | None = None,
) -> None: ...

def __init__(
self,
kcl: KCLayout,
width: int | None = None,
layer: kdb.LayerInfo | None = None,
sections: Sequence[tuple[kdb.LayerInfo, int, int] | tuple[kdb.LayerInfo, int]]
| None = None,
radius: int | None = None,
radius_min: int | None = None,
name: str | None = None,
bbox_layers: Sequence[kdb.LayerInfo] | None = None,
bbox_offsets: Sequence[int] | None = None,
base: SymmetricalCrossSection | None = None,
bbox_sections: list[tuple[kdb.LayerInfo, int, int, int, int]] | None = None,
) -> None:
if base:
base = kcl.get_cross_section(base)
else:
base = kcl.get_cross_section(
if not base:
if bbox_layers:
if not bbox_offsets:
bbox_offsets = [0 for _ in range(len(bbox_layers))]

Check warning on line 202 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L202

Added line #L202 was not covered by tests
else:
if not len(bbox_offsets) == len(bbox_layers):
raise ValueError(

Check warning on line 205 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L205

Added line #L205 was not covered by tests
"Length of the bbox_layers list and the bbox_offsets list"
" must be the same "
f"{len(bbox_layers)=}, {len(bbox_offsets)=}"
)
else:
bbox_layers = []
bbox_offsets = []

Check warning on line 212 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L211-L212

Added lines #L211 - L212 were not covered by tests
if width is None or layer is None or sections is None:
raise ValueError(

Check warning on line 214 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L214

Added line #L214 was not covered by tests
"If no base is given, width, layer, and sections must be defined"
)
base = kcl.get_symmetrical_cross_section(

Check warning on line 217 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L217

Added line #L217 was not covered by tests
SymmetricalCrossSection(
width=width,
enclosure=LayerEnclosure(sections=sections, main_layer=layer),
name=name,
bbox_sections={
s[0]: s[1]
for s in zip(bbox_layers, bbox_offsets) # noqa: B905
},
)
)
bbox_sections = bbox_sections or []
BaseModel.__init__(
self,
name=name or base.name,
kcl=kcl,
bbox_sections={s[0]: (s[1], s[2], s[3], s[4]) for s in bbox_sections},
)
self.kcl = kcl
self._base = base

Check warning on line 229 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L228-L229

Added lines #L228 - L229 were not covered by tests

@property
Expand All @@ -188,7 +237,7 @@ def sections(self) -> dict[kdb.LayerInfo, list[tuple[int | None, int]]]:
}

@property
def bbox_sections(self) -> dict[kdb.LayerInfo, tuple[int, int, int, int]]:
def bbox_sections(self) -> dict[kdb.LayerInfo, int]:
return self._base.bbox_sections.copy()

Check warning on line 241 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L241

Added line #L241 was not covered by tests

@property
Expand All @@ -209,35 +258,75 @@ def get_xmin_xmax(self) -> tuple[int, int]:


class DCrossSection(TCrossSection[float]):
@overload
def __init__(self, kcl: KCLayout, *, base: SymmetricalCrossSection) -> None: ...

@overload
def __init__(
self,
width: int,
kcl: KCLayout,
width: float,
layer: kdb.LayerInfo,
sections: list[tuple[kdb.LayerInfo, int, int] | tuple[kdb.LayerInfo, int]],
sections: list[
tuple[kdb.LayerInfo, float, float] | tuple[kdb.LayerInfo, float]
],
radius: float | None = None,
radius_min: float | None = None,
name: str | None = None,
bbox_layers: Sequence[kdb.LayerInfo] | None = None,
bbox_offsets: Sequence[float] | None = None,
) -> None: ...

def __init__(
self,
kcl: KCLayout,
radius: int | None = None,
radius_min: int | None = None,
width: float | None = None,
layer: kdb.LayerInfo | None = None,
sections: list[tuple[kdb.LayerInfo, float, float] | tuple[kdb.LayerInfo, float]]
| None = None,
radius: float | None = None,
radius_min: float | None = None,
name: str | None = None,
bbox_layers: Sequence[kdb.LayerInfo] | None = None,
bbox_offsets: Sequence[float] | None = None,
base: SymmetricalCrossSection | None = None,
bbox_sections: list[tuple[kdb.LayerInfo, int, int, int, int]] | None = None,
) -> None:
bbox_sections = bbox_sections or []
if base:
base = kcl.get_cross_section(base)
else:
base = kcl.get_cross_section(
if not base:
if bbox_layers:
if not bbox_offsets:
bbox_offsets = [0 for _ in range(len(bbox_layers))]

Check warning on line 297 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L297

Added line #L297 was not covered by tests
else:
if not len(bbox_offsets) == len(bbox_layers):
raise ValueError(

Check warning on line 300 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L300

Added line #L300 was not covered by tests
"Length of the bbox_layers list and the bbox_offsets list"
" must be the same "
f"{len(bbox_layers)=}, {len(bbox_offsets)=}"
)
else:
bbox_layers = []
bbox_offsets = []

Check warning on line 307 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L306-L307

Added lines #L306 - L307 were not covered by tests
if width is None or layer is None or sections is None:
raise ValueError(

Check warning on line 309 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L309

Added line #L309 was not covered by tests
"If no base is given, width, layer, and sections must be defined"
)
base = kcl.get_symmetrical_cross_section(

Check warning on line 312 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L312

Added line #L312 was not covered by tests
SymmetricalCrossSection(
width=width,
enclosure=LayerEnclosure(sections=sections, main_layer=layer),
width=kcl.to_dbu(width),
enclosure=LayerEnclosure(
sections=[
(s[0], *[kcl.to_dbu(s[i]) for i in range(1, len(s))]) # type: ignore[misc, arg-type]
for s in sections
],
main_layer=layer,
),
name=name,
bbox_sections={
s[0]: kcl.to_dbu(s[1])
for s in zip(bbox_layers, bbox_offsets) # noqa: B905
},
)
)
BaseModel.__init__(
self,
name=name or base.name,
kcl=kcl,
bbox_sections={s[0]: (s[1], s[2], s[3], s[4]) for s in bbox_sections},
)
self.kcl = kcl
self._base = base

Check warning on line 330 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L329-L330

Added lines #L329 - L330 were not covered by tests

@property
Expand All @@ -257,11 +346,20 @@ def sections(self) -> dict[kdb.LayerInfo, list[tuple[float | None, float]]]:
}

@property
def bbox_sections(self) -> dict[kdb.LayerInfo, tuple[float, float, float, float]]:
return {
k: tuple(self.kcl.to_um(e) for e in v) # type: ignore[misc]
for k, v in self._base.bbox_sections.items()
}
def bbox_sections(self) -> dict[kdb.LayerInfo, float]:
return {k: self.kcl.to_um(v) for k, v in self._base.bbox_sections.items()}

Check warning on line 350 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L350

Added line #L350 was not covered by tests

@property
def width(self) -> float:
return self.kcl.to_um(self._base.width)

Check warning on line 354 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L354

Added line #L354 was not covered by tests

@property
def radius(self) -> float | None:
return self.kcl.to_um(self._base.radius)

Check warning on line 358 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L358

Added line #L358 was not covered by tests

@property
def radius_min(self) -> float | None:
return self.kcl.to_um(self._base.radius_min)

Check warning on line 362 in src/kfactory/cross_section.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L362

Added line #L362 was not covered by tests

def get_xmin_xmax(self) -> tuple[float, float]:
xmax = self.kcl.to_um(self._base.get_xmax())
Expand Down
14 changes: 7 additions & 7 deletions src/kfactory/kcell.py
Original file line number Diff line number Diff line change
Expand Up @@ -792,7 +792,7 @@ def _get_ci(
kcell.name = cell.kcl.name + static_name_separator + cell.name
if cell.kcl.dbu != self.kcl.dbu:
for port, lib_port in zip(kcell.ports, cell.ports, strict=False):
port.cross_section = cell.kcl.get_cross_section(
port.cross_section = cell.kcl.get_symmetrical_cross_section(

Check warning on line 795 in src/kfactory/kcell.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/kcell.py#L795

Added line #L795 was not covered by tests
lib_port.cross_section.to_dtype(cell.kcl)
)
return ci
Expand Down Expand Up @@ -1431,7 +1431,7 @@ def get_meta_data(
self.create_port(
name=v.get("name"),
trans=trans_,
cross_section=self.kcl.get_cross_section(
cross_section=self.kcl.get_symmetrical_cross_section(
v["cross_section"]
),
port_type=v["port_type"],
Expand All @@ -1440,7 +1440,7 @@ def get_meta_data(
self.create_port(
name=v.get("name"),
dcplx_trans=v["dcplx_trans"],
cross_section=self.kcl.get_cross_section(
cross_section=self.kcl.get_symmetrical_cross_section(
v["cross_section"]
),
port_type=v["port_type"],
Expand All @@ -1451,10 +1451,10 @@ def get_meta_data(
v = port_dict[index]
trans_ = v.get("trans")
lib_kcl = kcls[lib_name]
cs = self.kcl.get_cross_section(
lib_kcl.get_cross_section(v["cross_section"]).to_dtype(
lib_kcl
)
cs = self.kcl.get_symmetrical_cross_section(
lib_kcl.get_symmetrical_cross_section(
v["cross_section"]
).to_dtype(lib_kcl)
)

if trans_ is not None:
Expand Down
Loading

0 comments on commit 7401e6b

Please sign in to comment.