Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ports tests #595

Merged
merged 17 commits into from
Jan 31, 2025
7 changes: 5 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ cov:
uv run pytest -n logical -s --cov=kfactory --cov-branch --cov-report=xml

dev-cov:
uv run pytest -n logical -s --cov=kfactory --cov-report=term-missing:skip-covered --durations=10
uv run pytest -n logical -s --cov=kfactory --cov-report=term-missing:skip-covered

venv:
uv venv -p 3.13

lint:
flake8 .
uv run ruff check .

mypy:
uv run dmypy run src/kfactory

pylint:
pylint kfactory
Expand Down
12 changes: 12 additions & 0 deletions src/kfactory/cross_section.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,12 @@
)
return self

@model_validator(mode="after")
def _validate_width(self) -> Self:
if self.width <= 0:
raise ValueError("Width must be greater than 0.")
return self

@cached_property
def main_layer(self) -> kdb.LayerInfo:
"""Main Layer of the enclosure and cross section."""
Expand All @@ -62,6 +68,12 @@
enclosure: DLayerEnclosure
name: str | None = None

@model_validator(mode="after")
def _validate_width(self) -> Self:
if self.width <= 0:
raise ValueError("Width must be greater than 0.")

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

View check run for this annotation

Codecov / codecov/patch

src/kfactory/cross_section.py#L74

Added line #L74 was not covered by tests
return self

def to_itype(self, kcl: KCLayout) -> SymmetricalCrossSection:
"""Convert to a dbu based CrossSection."""
return SymmetricalCrossSection(
Expand Down
10 changes: 1 addition & 9 deletions src/kfactory/enclosure.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@
field_validator,
model_serializer,
)
from ruamel.yaml.representer import BaseRepresenter, MappingNode
from typing_extensions import TypedDict

from . import kdb
Expand Down Expand Up @@ -475,7 +474,7 @@ class LayerSection(BaseModel):
touching or overlapping sections.
"""

sections: list[Section] = Field(default=[])
sections: list[Section] = Field(default_factory=list)

def add_section(self, sec: Section) -> int:
"""Add a new section.
Expand Down Expand Up @@ -541,7 +540,6 @@ class LayerEnclosure(BaseModel, validate_assignment=True, arbitrary_types_allowe
layer_sections: dict[kdb.LayerInfo, LayerSection]
_name: str | None = PrivateAttr()
main_layer: kdb.LayerInfo | None
yaml_tag: str = "!Enclosure"

def __init__(
self,
Expand Down Expand Up @@ -1042,12 +1040,6 @@ def bbox_reg(d_max: int, d_min: int | None = None) -> kdb.Region:

self.apply_custom(c, bbox_reg)

@classmethod
def to_yaml(cls, representer: BaseRepresenter, node: Any) -> MappingNode:
"""Get YAML representation of the enclosure."""
d = dict(node.enclosures)
return representer.represent_mapping(cls.yaml_tag, d)

def __str__(self) -> str:
"""String representation of an enclosure.

Expand Down
25 changes: 25 additions & 0 deletions src/kfactory/instance_ports.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,24 @@
class ProtoInstancePorts(HasCellPorts[TUnit], Generic[TUnit, TInstance], ABC):
instance: TInstance

@abstractmethod
def __len__(self) -> int: ...

@abstractmethod
def __contains__(self, port: str | ProtoPort[Any]) -> bool: ...

@abstractmethod
def __getitem__(self, key: int | str | None) -> ProtoPort[TUnit]: ...

@abstractmethod
def __iter__(self) -> Iterator[ProtoPort[TUnit]]: ...

def __repr__(self) -> str:
return f"{self.__class__.__name__}(n={len(self)})"

def __str__(self) -> str:
return f"{self.__class__.__name__}(ports={list(self)})"


class ProtoTInstancePorts(
ProtoInstancePorts[TUnit, ProtoTInstance[TUnit]], Generic[TUnit], ABC
Expand Down Expand Up @@ -414,6 +432,13 @@
"""Create a copy of the ports to iterate through."""
yield from (p.copy(self.instance.trans) for p in self.cell_ports)

def __contains__(self, port: str | ProtoPort[Any]) -> bool:
"""Check if a port is in the instance."""
if isinstance(port, ProtoPort):
return port.base in [p.base for p in self.instance.ports]
else:
return any(_port.name == port for _port in self.instance.ports)

Check warning on line 440 in src/kfactory/instance_ports.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/instance_ports.py#L440

Added line #L440 was not covered by tests

def __repr__(self) -> str:
"""String representation.

Expand Down
11 changes: 11 additions & 0 deletions src/kfactory/instances.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,17 @@
@abstractmethod
def clear(self) -> None: ...

def __repr__(self) -> str:
return f"{self.__class__.__name__}(n={len(self)})"

def __str__(self) -> str:
return f"{self.__class__.__name__}(insts={list(self)})"

Check warning on line 54 in src/kfactory/instances.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/instances.py#L54

Added line #L54 was not covered by tests

def __eq__(self, other: object) -> bool:
if not isinstance(other, ProtoInstances):
return False

Check warning on line 58 in src/kfactory/instances.py

View check run for this annotation

Codecov / codecov/patch

src/kfactory/instances.py#L58

Added line #L58 was not covered by tests
return list(self) == list(other)


class ProtoTInstances(ProtoInstances[TUnit, ProtoTInstance[TUnit]], ABC):
_tkcell: TKCell
Expand Down
48 changes: 31 additions & 17 deletions src/kfactory/kcell.py
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,15 @@
def kcl(self, val: KCLayout, /) -> None:
self._base_kcell.kcl = val

def __repr__(self) -> str:
"""Return a string representation of the Cell."""
port_names = [p.name for p in self.ports]
instances = [inst.name for inst in self.insts]
return (
f"{self.__class__.__name__}(name={self.name}, ports={port_names}, "
f"instances={instances}, locked={self.locked}, kcl={self.kcl.name})"
)


class TKCell(BaseKCell):
"""KLayout cell and change its class to KCell.
Expand Down Expand Up @@ -375,6 +384,9 @@
def lock(self) -> None:
self.kdb_cell.locked = True

def __repr__(self) -> str:
return f"{self.__class__.__name__}(name={self.kdb_cell.name})"


class TVCell(BaseKCell):
_locked: bool = PrivateAttr(default=False)
Expand Down Expand Up @@ -595,11 +607,6 @@
"""
self.plot()

def __repr__(self) -> str:
"""Return a string representation of the Cell."""
port_names = [p.name for p in self.ports]
return f"{self.name}: ports {port_names}, {len(self.insts)} instances"

def delete(self) -> None:
"""Delete the cell."""
ci = self.cell_index()
Expand Down Expand Up @@ -2450,28 +2457,25 @@
if verbose:
print(f"Building {d['name']}")
for _d in d.get("ports", Ports(ports=[], kcl=cell.kcl)):
layer_as_string = (
str(_d["layer"]).replace("[", "").replace("]", "").replace(", ", "/")
)
if "dcplx_trans" in _d:
p = cell.create_port(
cell.create_port(

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

View check run for this annotation

Codecov / codecov/patch

src/kfactory/kcell.py#L2464

Added line #L2464 was not covered by tests
name=str(_d["name"]),
dcplx_trans=kdb.DCplxTrans.from_s(_d["dcplx_trans"]),
width=cell.kcl.to_dbu(_d["dwidth"]),
layer=cell.kcl.layer(kdb.LayerInfo.from_string(_d["layer"])),
width=_d["dwidth"],
layer=cell.kcl.layer(kdb.LayerInfo.from_string(layer_as_string)),
port_type=_d["port_type"],
)
else:
p = cell.create_port(
cell.create_port(
name=str(_d["name"]),
trans=kdb.Trans.from_s(_d["trans"]),
width=cell.kcl.to_dbu(int(_d["width"])),
layer=cell.kcl.layer(kdb.LayerInfo.from_string(_d["layer"])),
width=int(_d["width"]),
layer=cell.kcl.layer(kdb.LayerInfo.from_string(layer_as_string)),
port_type=_d["port_type"],
)
p.info = Info(
**{
name: deserialize_setting(setting)
for name, setting in _d["info"].items()
}
)
cell.settings = KCellSettings(
**{
name: deserialize_setting(setting)
Expand Down Expand Up @@ -3482,6 +3486,16 @@
return key in self._kcl.tkcells
return False

def __repr__(self) -> str:
return f"{self.__class__.__name__}({self._kcl.name}, n={len(self)})"

def __str__(self) -> str:
return (
f"{self.__class__.__name__}({self._kcl.name}, {self._kcl.tkcells})".replace(
"TKCell", (self.__class__.__name__).replace("Cells", "Cell")
)
)


class DKCells(ProtoCells[DKCell]):
def __getitem__(self, key: int | str) -> DKCell:
Expand Down
2 changes: 1 addition & 1 deletion src/kfactory/layer.py
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ def __getitem__(self, key: str) -> LayerLevel:
"""Access layer stack elements."""
if key not in self.layers:
layers = list(self.layers.keys())
raise ValueError(f"{key!r} not in {layers}")
raise KeyError(f"{key!r} not in {layers}")

return self.layers[key]

Expand Down
3 changes: 3 additions & 0 deletions src/kfactory/layout.py
Original file line number Diff line number Diff line change
Expand Up @@ -1589,6 +1589,9 @@ def get_cross_section(
"""Get a cross section by name or specification."""
return self.cross_sections.get_cross_section(cross_section)

def __repr__(self) -> str:
return f"{self.__class__.__name__}({self.name}, n={len(self.kcells)})"


KCLayout.model_rebuild()
TVCell.model_rebuild()
Expand Down
Loading
Loading